소스 검색

Updates from 1.11.x

Julio Montoya 6 년 전
부모
커밋
9c217e952e
35개의 변경된 파일1236개의 추가작업 그리고 304개의 파일을 삭제
  1. 97 10
      main/announcements/announcements.php
  2. 5 6
      main/document/document.php
  3. 14 3
      main/document/download.php
  4. 64 26
      main/document/showinframes.php
  5. 19 2
      main/dropbox/dropbox_functions.inc.php
  6. 19 0
      main/gradebook/gradebook_add_cat.php
  7. 19 0
      main/gradebook/gradebook_add_eval.php
  8. 26 0
      main/gradebook/gradebook_add_link.php
  9. 4 3
      main/gradebook/gradebook_display_certificate.php
  10. 0 2
      main/gradebook/gradebook_display_summary.php
  11. 15 1
      main/gradebook/gradebook_edit_all.php
  12. 18 2
      main/gradebook/gradebook_edit_cat.php
  13. 10 0
      main/gradebook/gradebook_edit_eval.php
  14. 9 0
      main/gradebook/gradebook_edit_link.php
  15. 16 1
      main/gradebook/gradebook_edit_result.php
  16. 18 12
      main/gradebook/gradebook_statistics.php
  17. 140 12
      main/gradebook/gradebook_view_result.php
  18. 63 8
      main/gradebook/index.php
  19. 1 60
      main/gradebook/lib/GradebookUtils.php
  20. 31 1
      main/gradebook/lib/be/category.class.php
  21. 2 2
      main/gradebook/lib/be/evaluation.class.php
  22. 75 15
      main/gradebook/lib/be/exerciselink.class.php
  23. 29 0
      main/gradebook/lib/be/result.class.php
  24. 9 6
      main/gradebook/lib/fe/dataform.class.php
  25. 12 9
      main/gradebook/lib/fe/displaygradebook.php
  26. 6 1
      main/gradebook/lib/fe/evalform.class.php
  27. 6 2
      main/gradebook/lib/fe/flatviewtable.class.php
  28. 46 23
      main/gradebook/lib/fe/gradebooktable.class.php
  29. 65 2
      main/gradebook/lib/fe/resulttable.class.php
  30. 2 1
      main/gradebook/lib/gradebook_data_generator.class.php
  31. 10 1
      main/gradebook/my_certificates.php
  32. 155 0
      main/inc/ajax/announcement.ajax.php
  33. 27 1
      main/inc/lib/AnnouncementEmail.php
  34. 180 57
      main/inc/lib/AnnouncementManager.php
  35. 24 35
      main/inc/lib/document.lib.php

+ 97 - 10
main/announcements/announcements.php

@@ -488,9 +488,24 @@ switch ($action) {
             $announcement_to_modify = '';
         }
 
-        $announcementInfo = AnnouncementManager::get_by_id($courseId, $id);
-        if (isset($announcementInfo) && !empty($announcementInfo)) {
-            $to = AnnouncementManager::load_edit_users('announcement', $id);
+        $announcementInfo = [];
+        if (!empty($id)) {
+            $announcementInfo = AnnouncementManager::get_by_id($courseId, $id);
+        }
+
+        $showSubmitButton = true;
+        if (!empty($announcementInfo)) {
+            $to = AnnouncementManager::loadEditUsers('announcement', $id);
+
+            if (!empty($group_id)) {
+                $separated = CourseManager::separateUsersGroups($to);
+                if (isset($separated['groups']) && count($separated['groups']) > 1) {
+                    $form->freeze();
+                    Display::addFlash(Display::return_message(get_lang('LockByTeacher')));
+                    $showSubmitButton = false;
+                }
+            }
+
             $defaults = [
                 'title' => $announcementInfo['title'],
                 'content' => $announcementInfo['content'],
@@ -504,14 +519,52 @@ switch ($action) {
             }
         }
 
+        $ajaxUrl = api_get_path(WEB_AJAX_PATH).'announcement.ajax.php?'.api_get_cidreq().'&a=preview';
+
+        $form->addHtml("
+            <script>
+                $(document).on('ready', function () {
+                    $('#announcement_preview').on('click', function() {  
+                        var users = [];
+                        $('#users_to option').each(function() {
+                            users.push($(this).val());                            
+                        });
+                        
+                        var form = $('#announcement').serialize();
+                        $.ajax({
+                            type: 'POST',
+                            dataType: 'json',
+                            url: '".$ajaxUrl."',
+                            data: {users : JSON.stringify(users), form: form},  
+                            beforeSend: function() {
+                                $('#announcement_preview_result').html('<i class=\"fa fa-spinner\"></i>');
+                                $('#send_button').hide();
+                            },  
+                            success: function(result) {
+                                var resultToString = '';
+                                $.each(result, function(index, value) {
+                                    resultToString += '&nbsp;' + value;
+                                });
+                                $('#announcement_preview_result').html('' +
+                                    '".addslashes(get_lang('AnnouncementWillBeSentTo'))."<br/>' + resultToString
+                                );
+                                $('#announcement_preview_result').show();
+                                $('#send_button').show();                                
+                            }
+                        });
+                    });
+                });
+            </script>
+        ");
+
         if (isset($defaults['users'])) {
             foreach ($defaults['users'] as $value) {
                 $parts = explode(':', $value);
-
                 if (!isset($parts[1]) || empty($parts[1])) {
                     continue;
                 }
-                $form->addHtml("
+                $form->addHtml(
+                    "
                     <script>
                         $(document).on('ready', function () {
                             $('#choose_recipients').click();
@@ -558,16 +611,36 @@ switch ($action) {
         $config = api_get_configuration_value('announcements_hide_send_to_hrm_users');
 
         if ($config === false) {
-            $form->addCheckBox('send_to_hrm_users', null, get_lang('SendAnnouncementCopyToDRH'));
+            $form->addCheckBox(
+                'send_to_hrm_users',
+                null,
+                get_lang('SendAnnouncementCopyToDRH'),
+                ['id' => 'send_to_hrm_users']
+            );
         }
 
+        $form->addCheckBox('send_me_a_copy_by_email', null, get_lang('SendAnnouncementCopyToMyself'));
+        $defaults['send_me_a_copy_by_email'] = true;
+
+        if ($showSubmitButton) {
+            $form->addLabel('',
+                Display::url(
+                    get_lang('Preview'),
+                    'javascript:void(0)',
+                    ['class' => 'btn btn-default', 'id' => 'announcement_preview']
+                ).'<div id="announcement_preview_result" style="display:none"></div>'
+            );
+            $form->addHtml('<div id="send_button" style="display:none">');
         $form->addButtonSave(get_lang('ButtonPublishAnnouncement'));
+            $form->addHtml('</div>');
+        }
         $form->setDefaults($defaults);
 
         if ($form->validate()) {
             $data = $form->getSubmitValues();
             $data['users'] = isset($data['users']) ? $data['users'] : [];
             $sendToUsersInSession = isset($data['send_to_users_in_session']) ? true : false;
+            $sendMeCopy = isset($data['send_me_a_copy_by_email']) ? true : false;
 
             if (isset($id) && $id) {
                 // there is an Id => the announcement already exists => update mode
@@ -586,8 +659,9 @@ switch ($action) {
                     );
 
                     // Send mail
+                    $messageSentTo = [];
                     if (isset($_POST['email_ann']) && empty($_POST['onlyThoseMails'])) {
-                        AnnouncementManager::sendEmail(
+                        $messageSentTo = AnnouncementManager::sendEmail(
                             api_get_course_info(),
                             api_get_session_id(),
                             $id,
@@ -596,6 +670,11 @@ switch ($action) {
                         );
                     }
 
+                    if ($sendMeCopy && !in_array(api_get_user_id(), $messageSentTo)) {
+                        $email = new AnnouncementEmail(api_get_course_info(), api_get_session_id(), $id);
+                        $email->sendAnnouncementEmailToMySelf();
+                    }
+
                     Display::addFlash(
                         Display::return_message(
                             get_lang('AnnouncementModified'),
@@ -625,16 +704,17 @@ switch ($action) {
                             $sendToUsersInSession
                         );
                     } else {
-                        $insert_id = AnnouncementManager::add_group_announcement(
+                        $insert_id = AnnouncementManager::addGroupAnnouncement(
                             $data['title'],
                             $data['content'],
-                            ['GROUP:'.$group_id],
+                            $group_id,
                             $data['users'],
                             $file,
                             $file_comment,
                             $sendToUsersInSession
                         );
                     }
+
                     if ($insert_id) {
                         Display::addFlash(
                             Display::return_message(
@@ -644,14 +724,21 @@ switch ($action) {
                         );
 
                         // Send mail
+                        $messageSentTo = [];
                         if (isset($data['email_ann']) && $data['email_ann']) {
-                            AnnouncementManager::sendEmail(
+                            $messageSentTo = AnnouncementManager::sendEmail(
                                 api_get_course_info(),
                                 api_get_session_id(),
                                 $insert_id,
                                 $sendToUsersInSession
                             );
                         }
+
+                        if ($sendMeCopy && !in_array(api_get_user_id(), $messageSentTo)) {
+                            $email = new AnnouncementEmail(api_get_course_info(), api_get_session_id(), $insert_id);
+                            $email->sendAnnouncementEmailToMySelf();
+                        }
+
                         Security::clear_token();
                         header('Location: '.$homeUrl);
                         exit;

+ 5 - 6
main/document/document.php

@@ -209,7 +209,7 @@ if (!empty($groupId)) {
 }
 
 // Actions.
-$document_id = isset($_REQUEST['id']) ? intval($_REQUEST['id']) : null;
+$document_id = isset($_REQUEST['id']) ? (int) $_REQUEST['id'] : null;
 $currentUrl = api_get_self().'?'.api_get_cidreq().'&id='.$document_id;
 $curdirpath = isset($_GET['curdirpath']) ? Security::remove_XSS($_GET['curdirpath']) : null;
 
@@ -612,7 +612,7 @@ if (isset($document_id) && empty($action)) {
 
         $visibility = DocumentManager::check_visibility_tree(
             $document_id,
-            api_get_course_id(),
+            $courseInfo,
             $sessionId,
             api_get_user_id(),
             $groupIid
@@ -822,8 +822,7 @@ if (!$isAllowedToEdit && api_is_coach()) {
 
 /* Create shared folders */
 DocumentManager::createUserSharedFolder(api_get_user_id(), $courseInfo, $sessionId);
-Session::write('image_files_only', '');
-$image_files_only = '';
+
 if ($is_certificate_mode) {
     $interbreadcrumb[] = [
         'url' => '../gradebook/index.php?'.api_get_cidreq(),
@@ -1771,11 +1770,10 @@ if ($isAllowedToEdit ||
         );
     }
 }
-require 'document_slideshow.inc.php';
 if (!isset($_GET['keyword']) && !$is_certificate_mode) {
     $actionsLeft .= Display::url(
         Display::return_icon('slideshow.png', get_lang('ViewSlideshow'), '', ICON_SIZE_MEDIUM),
-        api_get_path(WEB_CODE_PATH).'document/slideshow.php?'.api_get_cidreq().'&curdirpath='.$curdirpathurl
+        api_get_path(WEB_CODE_PATH).'document/slideshow.php?'.api_get_cidreq().'&curdirpath='.$curdirpathurl.'&id='.$document_id
     );
 }
 
@@ -2231,4 +2229,5 @@ echo '
     </div>
 ';
 
+Session::erase('slideshow_'.api_get_course_id().api_get_session_id());
 Display::display_footer();

+ 14 - 3
main/document/download.php

@@ -26,10 +26,21 @@ $doc_url = str_replace('///', '&', $doc_url);
 // Still a space present? it must be a '+' (that got replaced by mod_rewrite)
 $doc_url = str_replace(' ', '+', $doc_url);
 
-$doc_url = str_replace(['../', '\\..', '\\0', '..\\'], ['', '', '', ''], $doc_url); //echo $doc_url;
+$docUrlParts = preg_split('/\/|\\\/', $doc_url);
+$doc_url = '';
 
-if (strpos($doc_url, '../') || strpos($doc_url, '/..')) {
-    $doc_url = '';
+foreach ($docUrlParts as $docUrlPart) {
+    if (empty($docUrlPart) || in_array($docUrlPart, ['.', '..', '0'])) {
+        continue;
+    }
+
+    $doc_url .= '/'.$docUrlPart;
+}
+
+if (empty($doc_url)) {
+    api_not_allowed(
+        !empty($_GET['origin']) && $_GET['origin'] === 'learnpath'
+    );
 }
 
 // Dealing with image included into survey: when users receive a link towards a

+ 64 - 26
main/document/showinframes.php

@@ -91,10 +91,11 @@ if ($is_allowed_in_course == false) {
 // Check user visibility.
 $is_visible = DocumentManager::check_visibility_tree(
     $document_id,
-    api_get_course_id(),
+    api_get_course_info(),
     api_get_session_id(),
     api_get_user_id(),
-    api_get_group_id()
+    api_get_group_id(),
+    false
 );
 
 if (!$is_allowed_to_edit && !$is_visible) {
@@ -169,26 +170,11 @@ header('Pragma: no-cache');
 $browser_display_title = 'Documents - '.Security::remove_XSS($_GET['cidReq']).' - '.$file;
 // Only admins get to see the "no frames" link in pageheader.php, so students get a header that's not so high
 $frameheight = 135;
-if ($is_courseAdmin) {
+if (api_is_course_admin()) {
     $frameheight = 165;
 }
 
-$js_glossary_in_documents = '
-  $.frameReady(function(){
-   //  $("<div>I am a div courses</div>").prependTo("body");
-  }, "top.mainFrame",
-  {
-    load: [
-        { type:"script", id:"_fr1", src:"'.api_get_jquery_web_path().'"},
-        { type:"script", id:"_fr7", src:"'.api_get_path(WEB_PUBLIC_PATH).'assets/MathJax/MathJax.js?config=AM_HTMLorMML"},
-        { type:"script", id:"_fr4", src:"'.api_get_path(WEB_PUBLIC_PATH).'assets/jquery-ui/jquery-ui.min.js"},
-        { type:"stylesheet", id:"_fr5", src:"'.api_get_path(WEB_PUBLIC_PATH).'assets/jquery-ui/themes/smoothness/jquery-ui.min.css"},
-        { type:"stylesheet", id:"_fr6", src:"'.api_get_path(WEB_PUBLIC_PATH).'assets/jquery-ui/themes/smoothness/theme.css"},
-        { type:"script", id:"_fr2", src:"'.api_get_path(WEB_LIBRARY_PATH).'javascript/jquery.highlight.js"},
-        { type:"script", id:"_fr3", src:"'.api_get_path(WEB_CODE_PATH).'glossary/glossary.js.php?'.api_get_cidreq().'"},
-        { type:"script", id:"_fr4", src:"'.api_get_path(WEB_AJAX_PATH).'lang.ajax.php?a=translate_html&'.api_get_cidreq().'"},
-    ]
-  });';
+$frameReady = Display::getFrameReadyBlock('top.mainFrame');
 
 $web_odf_supported_files = DocumentManager::get_web_odf_extension_list();
 // PDF should be displayed with viewerJS
@@ -208,6 +194,7 @@ if (in_array(strtolower($pathinfo['extension']), $web_odf_supported_files)) {
     </script>'
     ;
 }
+
 // Activate code highlight.
 $isChatFolder = false;
 if (isset($document_data['parents']) && isset($document_data['parents'][0])) {
@@ -244,8 +231,6 @@ if ($is_freemind_available) {
     $execute_iframe = false;
 }
 
-$execute_iframe = true;
-
 if (!$playerSupported && $execute_iframe) {
     $htmlHeadXtra[] = '<script>
     <!--
@@ -258,16 +243,14 @@ if (!$playerSupported && $execute_iframe) {
             my_iframe = document.getElementById("mainFrame");
             if (my_iframe) {
                 //this doesnt seem to work in IE 7,8,9
-                new_height = my_iframe.contentWindow.document.body.scrollHeight;
-                my_iframe.height = my_iframe.contentWindow.document.body.scrollHeight + "px";
+                my_iframe.height = my_iframe.contentWindow.document.body.scrollHeight + 50 + "px";
             }
         };
 
         // Fixes the content height of the frame
         window.onload = function() {
             updateContentHeight();
-            '.$js_glossary_in_documents.'
-
+            '.$frameReady.'
         }
     </script>';
 }
@@ -372,7 +355,62 @@ if ($execute_iframe) {
         $content = Security::remove_XSS(file_get_contents($file_url_sys));
         echo $content;
     } else {
-        echo '<iframe id="mainFrame" name="mainFrame" border="0" frameborder="0" scrolling="no" style="width:100%;" height="600" src="'.$file_url_web.'&rand='.mt_rand(1, 10000).'" height="500" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true"></iframe>';
+        if (api_is_allowed_to_edit()) {
+            $parentId = $document_data['parent_id'];
+            $url = api_get_path(WEB_CODE_PATH).'document/document.php?'.api_get_cidreq().'&id='.$parentId;
+            $actionsLeft = Display::url(
+                Display::return_icon('back.png', get_lang('Back'), '', ICON_SIZE_MEDIUM),
+                $url
+            );
+
+            $actionsLeft .= Display::url(
+                Display::return_icon(
+                    'edit.png',
+                    get_lang('Modify'),
+                    '',
+                    ICON_SIZE_MEDIUM
+                ),
+                api_get_path(WEB_CODE_PATH).'document/edit_document.php?'.api_get_cidreq().'&id='.$document_id
+            );
+
+            $titleToShow = addslashes(basename($document_data['title']));
+
+            $urlDeleteParams = http_build_query(
+                [
+                    'action' => 'delete_item',
+                    'id' => $parentId,
+                    'deleteid' => $document_data['id'],
+                ]
+            );
+            $actionsLeft .= Display::url(
+                Display::return_icon('delete.png', get_lang('Delete'), '', ICON_SIZE_MEDIUM),
+                '#',
+                [
+                    'data-item-title' => $titleToShow,
+                    'data-href' => api_get_path(WEB_CODE_PATH).'document/document.php?'.api_get_cidreq(
+                        ).'&'.$urlDeleteParams,
+                    'data-toggle' => 'modal',
+                    'data-target' => '#confirm-delete',
+                ]
+            );
+            $actionsLeft .= Display::url(
+                Display::return_icon('pdf.png', get_lang('Export2PDF'), [], ICON_SIZE_MEDIUM),
+                api_get_path(WEB_CODE_PATH).'document/document.php?'.api_get_cidreq(
+                ).'&action=export_to_pdf&id='.$document_id
+            );
+
+            echo $toolbar = Display::toolbarAction('actions-documents', [$actionsLeft]);
+        }
+
+        echo '<iframe 
+            id="mainFrame" 
+            name="mainFrame" 
+            border="0" 
+            frameborder="0" 
+            scrolling="no" 
+            style="width:100%;" height="600" 
+            src="'.$file_url_web.'&rand='.mt_rand(1, 10000).'" 
+            height="500" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true"></iframe>';
     }
 }
 Display::display_footer();

+ 19 - 2
main/dropbox/dropbox_functions.inc.php

@@ -564,7 +564,14 @@ function display_add_form($viewReceivedCategory, $viewSentCategory, $view, $id =
                 api_get_session_id(),
                 null,
                 null,
-                0
+                0,
+                false,
+                false,
+                false,
+                [],
+                [],
+                [],
+                true
             );
         }
 
@@ -602,7 +609,17 @@ function display_add_form($viewReceivedCategory, $viewSentCategory, $view, $id =
         if (api_get_setting('dropbox_allow_student_to_student') == 'true' || $_user['status'] != STUDENT) {
             $complete_user_list_for_dropbox = CourseManager::get_user_list_from_course_code(
                 $course_info['code'],
-                api_get_session_id()
+                api_get_session_id(),
+                null,
+                null,
+                null,
+                false,
+                false,
+                false,
+                [],
+                [],
+                [],
+                true
             );
         } else {
             $complete_user_list_for_dropbox = CourseManager::get_teacher_list_from_course_code(

+ 19 - 0
main/gradebook/gradebook_add_cat.php

@@ -89,10 +89,29 @@ if ($form->validate()) {
     }
     $cat->set_visible($visible);
     $result = $cat->add();
+
+    $logInfo = [
+        'tool' => TOOL_GRADEBOOK,
+        'tool_id' => 0,
+        'tool_id_detail' => 0,
+        'action' => 'new-cat',
+        'action_details' => 'parent_id='.$cat->get_parent_id(),
+    ];
+    Event::registerLog($logInfo);
+
     header('Location: '.Category::getUrl().'addcat=&selectcat='.$cat->get_parent_id());
     exit;
 }
 
+$logInfo = [
+    'tool' => TOOL_GRADEBOOK,
+    'tool_id' => 0,
+    'tool_id_detail' => 0,
+    'action' => 'add-cat',
+    'action_details' => Category::getUrl().'selectcat='.$get_select_cat,
+];
+Event::registerLog($logInfo);
+
 if (!$_in_course) {
     $interbreadcrumb[] = [
         'url' => Category::getUrl().'selectcat='.$get_select_cat,

+ 19 - 0
main/gradebook/gradebook_add_eval.php

@@ -64,6 +64,16 @@ if ($form->validate()) {
     }
     $eval->set_visible($visible);
     $eval->add();
+
+    $logInfo = [
+        'tool' => TOOL_GRADEBOOK,
+        'tool_id' => 0,
+        'tool_id_detail' => 0,
+        'action' => 'new-eval',
+        'action_details' => 'selectcat='.$eval->get_category_id(),
+    ];
+    Event::registerLog($logInfo);
+
     if ($eval->get_course_code() == null) {
         if ($values['adduser'] == 1) {
             //Disabling code when course code is null see issue #2705
@@ -85,6 +95,15 @@ if ($form->validate()) {
     }
 }
 
+$logInfo = [
+    'tool' => TOOL_GRADEBOOK,
+    'tool_id' => 0,
+    'tool_id_detail' => 0,
+    'action' => 'add-eval',
+    'action_details' => 'selectcat='.$select_cat,
+];
+Event::registerLog($logInfo);
+
 $interbreadcrumb[] = [
     'url' => Category::getUrl().'selectcat='.$select_cat,
     'name' => get_lang('Gradebook'), ]

+ 26 - 0
main/gradebook/gradebook_add_link.php

@@ -131,6 +131,16 @@ if (isset($typeSelected) && $typeSelected != '0') {
         }
 
         $link->add();
+
+        $logInfo = [
+            'tool' => TOOL_GRADEBOOK,
+            'tool_id' => 0,
+            'tool_id_detail' => 0,
+            'action' => 'new-link',
+            'action_details' => 'selectcat='.$selectCat,
+        ];
+        Event::registerLog($logInfo);
+
         $addvalue_result = !empty($addvalues['addresult']) ? $addvalues['addresult'] : [];
         if ($addvalue_result == 1) {
             header('Location: gradebook_add_result.php?selecteval='.$link->get_ref_id().'&'.api_get_cidreq());
@@ -142,6 +152,22 @@ if (isset($typeSelected) && $typeSelected != '0') {
     }
 }
 
+$action_details = '';
+$current_id = 0;
+if (isset($_GET['selectcat'])) {
+    $action_details = 'selectcat';
+    $current_id = (int) $_GET['selectcat'];
+}
+
+$logInfo = [
+    'tool' => TOOL_GRADEBOOK,
+    'tool_id' => 0,
+    'tool_id_detail' => 0,
+    'action' => 'add-link',
+    'action_details' => 'selectcat='.$selectCat,
+];
+Event::registerLog($logInfo);
+
 $interbreadcrumb[] = [
     'url' => Category::getUrl().'selectcat='.$selectCat,
     'name' => get_lang('Gradebook'),

+ 4 - 3
main/gradebook/gradebook_display_certificate.php

@@ -85,7 +85,7 @@ switch ($action) {
         $currentUserInfo = api_get_user_info();
         $message = isset($_POST['message']) ? $_POST['message'] : '';
         $subject = get_lang('NotificationCertificateSubject');
-        if (!empty($originalMessage)) {
+        if (!empty($message)) {
             foreach ($certificate_list as $index => $value) {
                 $userInfo = api_get_user_info($value['user_id']);
                 if (empty($userInfo)) {
@@ -131,7 +131,8 @@ switch ($action) {
         if (api_get_plugin_setting('customcertificate', 'enable_plugin_customcertificate') == 'true' &&
             api_get_course_setting('customcertificate_course_enable', $courseCode) == 1
         ) {
-            $url = api_get_path(WEB_PLUGIN_PATH).'customcertificate/src/print_certificate.php?export_all=1';
+            $params = 'course_code='.api_get_course_id().'&session_id='.api_get_session_id().'&'.api_get_cidreq();
+            $url = api_get_path(WEB_PLUGIN_PATH).'customcertificate/src/print_certificate.php?export_all=1&'.$params;
         } else {
             if (api_is_student_boss()) {
                 $userGroup = new UserGroup();
@@ -307,7 +308,7 @@ if (count($certificate_list) == 0) {
             echo '<td width="50%">'.get_lang('Score').' : '.$valueCertificate['score_certificate'].'</td>';
             echo '<td width="30%">'.get_lang('Date').' : '.api_convert_and_format_date($valueCertificate['created_at']).'</td>';
             echo '<td width="20%">';
-            $url = api_get_path(WEB_PATH).'certificates/index.php?id='.$valueCertificate['id'];
+            $url = api_get_path(WEB_PATH).'certificates/index.php?id='.$valueCertificate['id'].'&user_id='.$value['user_id'];
             $certificates = Display::url(
                 get_lang('Certificate'),
                 $url,

+ 0 - 2
main/gradebook/gradebook_display_summary.php

@@ -4,8 +4,6 @@
 use ChamiloSession as Session;
 
 /**
- * Script.
- *
  * @package chamilo.gradebook
  */
 require_once __DIR__.'/../inc/global.inc.php';

+ 15 - 1
main/gradebook/gradebook_edit_all.php

@@ -16,12 +16,26 @@ api_protect_course_script(true);
 api_block_anonymous_users();
 GradebookUtils::block_students();
 
-$my_selectcat = isset($_GET['selectcat']) ? intval($_GET['selectcat']) : 0;
+$my_selectcat = isset($_GET['selectcat']) ? (int) $_GET['selectcat'] : 0;
 
 if (empty($my_selectcat)) {
     api_not_allowed(true);
 }
 
+$action_details = '';
+if (isset($_GET['selectcat'])) {
+    $action_details = 'selectcat';
+}
+
+$logInfo = [
+    'tool' => TOOL_GRADEBOOK,
+    'tool_id' => 0,
+    'tool_id_detail' => 0,
+    'action' => 'edit-weight',
+    'action_details' => $action_details,
+];
+Event::registerLog($logInfo);
+
 $course_id = GradebookUtils::get_course_id_by_link_id($my_selectcat);
 $table_link = Database::get_main_table(TABLE_MAIN_GRADEBOOK_LINK);
 $table_evaluation = Database::get_main_table(TABLE_MAIN_GRADEBOOK_EVALUATION);

+ 18 - 2
main/gradebook/gradebook_edit_cat.php

@@ -10,7 +10,6 @@ require_once __DIR__.'/../inc/global.inc.php';
 
 api_block_anonymous_users();
 GradebookUtils::block_students();
-
 $edit_cat = isset($_REQUEST['editcat']) ? intval($_REQUEST['editcat']) : '';
 
 $catedit = Category::load($edit_cat);
@@ -84,7 +83,24 @@ if ($form->validate()) {
     header('Location: '.Category::getUrl().'editcat=&selectcat='.$cat->get_parent_id());
     exit;
 }
-$selectcat = isset($_GET['selectcat']) ? (int) $_GET['selectcat'] : '';
+$selectcat = isset($_GET['selectcat']) ? (int) $_GET['selectcat'] : 0;
+
+$action_details = '';
+$current_id = 0;
+if (isset($_GET['editcat'])) {
+    $action_details = 'editcat';
+    $current_id = (int) $_GET['editcat'];
+}
+
+$logInfo = [
+    'tool' => TOOL_GRADEBOOK,
+    'tool_id' => 0,
+    'tool_id_detail' => 0,
+    'action' => 'edit-cat',
+    'action_details' => $action_details,
+];
+Event::registerLog($logInfo);
+
 $interbreadcrumb[] = [
     'url' => Category::getUrl().'selectcat='.$selectcat,
     'name' => get_lang('Gradebook'),

+ 10 - 0
main/gradebook/gradebook_edit_eval.php

@@ -44,6 +44,16 @@ if ($form->validate()) {
     }
     $eval->set_visible($visible);
     $eval->save();
+
+    $logInfo = [
+        'tool' => TOOL_GRADEBOOK,
+        'tool_id' => 0,
+        'tool_id_detail' => 0,
+        'action' => 'edit-eval',
+        'action_details' => '',
+    ];
+    Event::registerLog($logInfo);
+
     header('Location: '.Category::getUrl().'editeval=&selectcat='.$eval->get_category_id());
     exit;
 }

+ 9 - 0
main/gradebook/gradebook_edit_link.php

@@ -105,6 +105,15 @@ if ($form->validate()) {
             'type' => LINK_STUDENTPUBLICATION,
         ]);
 
+    $logInfo = [
+        'tool' => TOOL_GRADEBOOK,
+        'tool_id' => 0,
+        'tool_id_detail' => 0,
+        'action' => 'edit-link',
+        'action_details' => '',
+    ];
+    Event::registerLog($logInfo);
+
     header('Location: '.Category::getUrl().'linkedited=&selectcat='.$link->get_category_id());
     exit;
 }

+ 16 - 1
main/gradebook/gradebook_edit_result.php

@@ -37,8 +37,23 @@ if ($edit_result_form->validate()) {
         if (empty($score)) {
             $score = 0;
         }
-        $result->set_score(api_number_format($score, api_get_setting('gradebook_number_decimals')));
+        $score = api_number_format($score, api_get_setting('gradebook_number_decimals'));
+        $result->set_score($score);
         $result->save();
+
+        $allowMultipleAttempts = api_get_configuration_value('gradebook_multiple_evaluation_attempts');
+        if ($allowMultipleAttempts) {
+            $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_RESULT_ATTEMPT);
+            $now = api_get_utc_datetime();
+            $params = [
+                'result_id' => $result->get_id(),
+                'score' => $score,
+                'comment' => '',
+                'created_at' => $now,
+                'updated_at' => $now,
+            ];
+            Database::insert($table, $params);
+        }
     }
     Display::addFlash(Display::return_message(get_lang('AllResultsEdited')));
     header('Location: gradebook_view_result.php?selecteval='.$select_eval.'&'.api_get_cidreq());

+ 18 - 12
main/gradebook/gradebook_statistics.php

@@ -10,13 +10,20 @@ require_once __DIR__.'/../inc/global.inc.php';
 
 api_block_anonymous_users();
 
-$eval = Evaluation :: load($_GET['selecteval']);
-if ($eval[0]->get_category_id() < 0) {
+$categoryId = (int) $_GET['selecteval'];
+$eval = Evaluation::load($categoryId);
+if (!isset($eval[0])) {
+    api_not_allowed(true);
+}
+    // if category id is negative, then the evaluation's origin is a link
+$eval = $eval[0];
+
+if ($eval->get_category_id() < 0) {
     // if category id is negative, then the evaluation's origin is a link
-    $link = LinkFactory::get_evaluation_link($eval[0]->get_id());
+    $link = LinkFactory::get_evaluation_link($eval->get_id());
     $currentcat = Category::load($link->get_category_id());
 } else {
-    $currentcat = Category::load($eval[0]->get_category_id());
+    $currentcat = Category::load($eval->get_category_id());
 }
 
 $interbreadcrumb[] = [
@@ -26,7 +33,7 @@ $interbreadcrumb[] = [
 
 if (api_is_allowed_to_edit()) {
     $interbreadcrumb[] = [
-        'url' => 'gradebook_view_result.php?selecteval='.intval($_GET['selecteval']).'&'.api_get_cidreq(),
+        'url' => 'gradebook_view_result.php?selecteval='.$categoryId.'&'.api_get_cidreq(),
         'name' => get_lang('ViewResult'),
     ];
 }
@@ -34,7 +41,7 @@ $displayScore = ScoreDisplay::instance();
 
 Display::display_header(get_lang('EvaluationStatistics'));
 DisplayGradebook::display_header_result(
-    $eval[0],
+    $eval,
     $currentcat[0]->get_id(),
     0,
     'statistics'
@@ -48,7 +55,7 @@ if (!$displayScore->is_custom() || empty($displays)) {
         echo Display::return_message(get_lang('PleaseEnableScoringSystem'), 'error', false);
     }
 } else {
-    $allresults = Result::load(null, null, $eval[0]->get_id());
+    $allresults = Result::load(null, null, $eval->get_id());
     $nr_items = [];
     foreach ($displays as $itemsdisplay) {
         $nr_items[$itemsdisplay['display']] = 0;
@@ -59,7 +66,7 @@ if (!$displayScore->is_custom() || empty($displays)) {
         $score = $result->get_score();
         if (isset($score)) {
             $display = $displayScore->display_score(
-                [$score, $eval[0]->get_max()],
+                [$score, $eval->get_max()],
                 SCORE_CUSTOM,
                 SCORE_ONLY_CUSTOM,
                 true
@@ -82,19 +89,18 @@ if (!$displayScore->is_custom() || empty($displays)) {
     $stattable = '<table class="data_table" cellspacing="0" cellpadding="3">';
     $stattable .= '<tr><th>'.get_lang('ScoringSystem').'</th>';
     $stattable .= '<th>'.get_lang('Percentage').'</th>';
-    $stattable .= '<th>'.get_lang('CountUsers').'</th>';
-    //$stattable .= '<th>' . get_lang('Statistics') . '</th></tr>';
+    $stattable .= '<th>'.get_lang('CountUsers').'</th></tr>';
     $counter = 0;
     foreach ($keys as $key) {
         $bar = ($highest_ratio > 0 ? ($nr_items[$key] / $highest_ratio) * 100 : 0);
         $stattable .= '<tr class="row_'.($counter % 2 == 0 ? 'odd' : 'even').'">';
         $stattable .= '<td width="150">'.$key.'</td>';
-
         $stattable .= '<td width="550">'.Display::bar_progress($bar).'</td>';
         $stattable .= '<td align="right">'.$nr_items[$key].'</td>';
+        $stattable .= '</tr>';
         $counter++;
     }
-    $stattable .= '</tr></table>';
+    $stattable .= '</table>';
     echo $stattable;
 }
 Display :: display_footer();

+ 140 - 12
main/gradebook/gradebook_view_result.php

@@ -54,6 +54,127 @@ if (isset($_GET['selecteval'])) {
     $iscourse = !empty(api_get_course_id());
 }
 
+$allowMultipleAttempts = api_get_configuration_value('gradebook_multiple_evaluation_attempts');
+
+if (isset($_GET['action'])) {
+    switch ($_GET['action']) {
+        case 'delete_attempt':
+            $result = Result::load($_GET['editres']);
+            if ($allowMultipleAttempts && !empty($result) && isset($result[0]) && api_is_allowed_to_edit()) {
+                /** @var Result $result */
+                $result = $result[0];
+                $url = api_get_self().'?selecteval='.$select_eval.'&'.api_get_cidreq().'&editres='.$result->get_id();
+                $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_RESULT_ATTEMPT);
+                if (isset($_GET['result_attempt_id'])) {
+                    $attemptId = (int) $_GET['result_attempt_id'];
+                    $sql = "DELETE FROM $table WHERE result_id = ".$result->get_id()." AND id = $attemptId";
+                    Database::query($sql);
+                    Display::addFlash(Display::return_message(get_lang('Deleted')));
+                }
+
+                header('Location: '.$url.'&action=add_attempt');
+                exit;
+            }
+            break;
+        case 'add_attempt':
+            $result = Result::load($_GET['editres']);
+            if ($allowMultipleAttempts && !empty($result) && isset($result[0]) && api_is_allowed_to_edit()) {
+                /** @var Result $result */
+                $result = $result[0];
+                $backUrl = api_get_self().'?selecteval='.$select_eval.'&'.api_get_cidreq();
+                $interbreadcrumb[] = [
+                    'url' => $backUrl,
+                    'name' => get_lang('Details'),
+                ];
+
+                /** @var Evaluation $evaluation */
+                $evaluation = $eval[0];
+
+                $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_RESULT_ATTEMPT);
+                $now = api_get_utc_datetime();
+
+                $url = api_get_self().'?selecteval='.$select_eval.'&'.api_get_cidreq().'&editres='.$result->get_id();
+                $form = new FormValidator('attempt', 'post', $url.'&action=add_attempt');
+                $form->addHeader(get_lang('AddResult'));
+                $form->addLabel(get_lang('CurrentScore'), $result->get_score());
+
+                $form->addFloat(
+                    'score',
+                    [
+                        get_lang('Score'),
+                        null,
+                        '/ '.$evaluation->get_max(),
+                    ],
+                    true,
+                    [
+                        'size' => '4',
+                        'maxlength' => '5',
+                    ],
+                    false,
+                    0,
+                    $evaluation->get_max()
+                );
+
+                $form->addTextarea('comment', get_lang('Comment'));
+
+                /*$form->addRule(
+                    'score',
+                    get_lang('ValueTooBig'),
+                    'max_numeric_length',
+                    $evaluation->get_max()
+                );*/
+                $form->addButtonSave(get_lang('Save'));
+                $attemptList = ResultTable::getResultAttemptTable($result, $url);
+                $form->addLabel(get_lang('Attempts'), $attemptList);
+
+                if ($form->validate()) {
+                    $values = $form->getSubmitValues();
+                    $newScore = $values['score'];
+
+                    $newScore = api_number_format(
+                        $newScore,
+                        api_get_setting('gradebook_number_decimals')
+                    );
+
+                    $params = [
+                        'result_id' => $result->get_id(),
+                        'score' => $newScore,
+                        'comment' => $values['comment'],
+                        'created_at' => $now,
+                        'updated_at' => $now,
+                    ];
+
+                    Database::insert($table, $params);
+                    if ($newScore > $result->get_score()) {
+                        $result->set_score($newScore);
+                        $result->save();
+                    }
+                    Display::addFlash(Display::return_message(get_lang('Saved')));
+                    header('Location: '.api_get_self().'?selecteval='.$select_eval.'&'.api_get_cidreq());
+                    exit;
+                }
+
+                Display::display_header();
+
+                $items[] = [
+                    'url' => $backUrl,
+                    'content' => Display::return_icon(
+                        'back.png',
+                        get_lang('Back'),
+                        [],
+                        ICON_SIZE_MEDIUM
+                    ),
+                ];
+
+                echo Display::actions($items);
+                $form->display();
+                Display::display_footer();
+                exit;
+            }
+            break;
+    }
+}
+
 if (isset($_GET['editres'])) {
     $edit_res_xml = Security::remove_XSS($_GET['editres']);
     $resultedit = Result :: load($edit_res_xml);
@@ -76,15 +197,27 @@ if (isset($_GET['editres'])) {
         $result->set_evaluation_id($select_eval);
         $row_value = isset($values['score']) ? $values['score'] : 0;
         if (!empty($row_value) || $row_value == 0) {
-            $result->set_score(
-                api_number_format(
-                    $row_value,
-                    api_get_setting('gradebook_number_decimals')
-                )
+            $row_value = api_number_format(
+                $row_value,
+                api_get_setting('gradebook_number_decimals')
             );
+            $result->set_score($row_value);
         }
         $result->save();
-        unset($result);
+
+        if ($allowMultipleAttempts && !empty($result->get_id())) {
+            $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_RESULT_ATTEMPT);
+            $now = api_get_utc_datetime();
+            $params = [
+                'result_id' => $result->get_id(),
+                'score' => $row_value,
+                'comment' => $values['comment'],
+                'created_at' => $now,
+                'updated_at' => $now,
+            ];
+
+            Database::insert($table, $params);
+        }
 
         Display::addFlash(Display::return_message(get_lang('ResultEdited'), 'normal', false));
         header('Location: gradebook_view_result.php?selecteval='.$select_eval.'&editresmessage=&'.api_get_cidreq());
@@ -114,12 +247,7 @@ if (isset($_GET['import'])) {
             $values = $import_result_form->exportValues();
             $file_type = $_POST['file_type'];
             $file_name = $_FILES['import_file']['tmp_name'];
-            if ($file_type == 'csv') {
-                $results = Import :: csvToArray($file_name);
-            } else {
-                $results = parse_xml_data($file_name);
-            }
-
+            $results = Import :: csvToArray($file_name);
             $nr_results_added = 0;
             foreach ($results as $index => $importedresult) {
                 //check username & score

+ 63 - 8
main/gradebook/index.php

@@ -66,6 +66,62 @@ $(document).ready(function() {
 });
 </script>';
 
+$list_actions = [];
+$list_values = [];
+if (isset($_GET['movecat'])) {
+    $list_actions[] = 'movecat';
+    $list_values[] = $_GET['movecat'];
+}
+if (isset($_GET['moveeval'])) {
+    $list_actions[] = 'moveeval';
+    $list_values[] = $_GET['moveeval'];
+}
+if (isset($_GET['movelink'])) {
+    $list_actions[] = 'movelink';
+    $list_values[] = $_GET['movelink'];
+}
+if (isset($_GET['visiblecat'])) {
+    $list_actions[] = 'visiblecat';
+    $list_values[] = $_GET['visiblecat'];
+}
+if (isset($_GET['deletecat'])) {
+    $list_actions[] = 'deletecat';
+    $list_values[] = $_GET['deletecat'];
+}
+if (isset($_GET['visibleeval'])) {
+    $list_actions[] = 'visibleeval';
+    $list_values[] = $_GET['visibleeval'];
+}
+if (isset($_GET['lockedeval'])) {
+    $list_actions[] = 'lockedeval';
+    $list_values[] = $_GET['lockedeval'];
+}
+if (isset($_GET['deleteeval'])) {
+    $list_actions[] = 'deleteeval';
+    $list_values[] = $_GET['deleteeval'];
+}
+if (isset($_GET['visiblelink'])) {
+    $list_actions[] = 'visiblelink';
+    $list_values[] = $_GET['visiblelink'];
+}
+if (isset($_GET['deletelink'])) {
+    $list_actions[] = 'deletelink';
+    $list_values[] = $_GET['deletelink'];
+}
+if (isset($_GET['action'])) {
+    $list_actions[] = $_GET['action'];
+}
+$my_actions = implode(';', $list_actions);
+$my_actions_values = implode(';', $list_values);
+$logInfo = [
+    'tool' => TOOL_GRADEBOOK,
+    'tool_id' => 0,
+    'tool_id_detail' => 0,
+    'action' => $my_actions,
+    'action_details' => $my_actions_values,
+];
+Event::registerLog($logInfo);
+
 $tbl_forum_thread = Database::get_course_table(TABLE_FORUM_THREAD);
 $tbl_attendance = Database::get_course_table(TABLE_ATTENDANCE);
 $tbl_grade_links = Database::get_main_table(TABLE_MAIN_GRADEBOOK_LINK);
@@ -772,6 +828,12 @@ if (!api_is_allowed_to_edit(null, true)) {
 if (isset($first_time) && $first_time == 1 && api_is_allowed_to_edit(null, true)) {
     echo '<meta http-equiv="refresh" content="0;url='.api_get_self().'?'.api_get_cidreq().'" />';
 } else {
+    // Tool introduction
+    Display::display_introduction_section(
+        TOOL_GRADEBOOK,
+        ['ToolbarSet' => 'AssessmentsIntroduction']
+    );
+
     if (!empty($actionsLeft)) {
         echo $toolbar = Display::toolbarAction(
             'gradebook-student-actions',
@@ -780,12 +842,6 @@ if (isset($first_time) && $first_time == 1 && api_is_allowed_to_edit(null, true)
     }
 
     if (api_is_allowed_to_edit(null, true)) {
-        // Tool introduction
-        Display::display_introduction_section(
-            TOOL_GRADEBOOK,
-            ['ToolbarSet' => 'AssessmentsIntroduction']
-        );
-
         if (((empty($selectCat)) || (isset($_GET['cidReq']) && $_GET['cidReq'] !== '')) ||
             (isset($_GET['isStudentView']) && $_GET['isStudentView'] == 'false')
         ) {
@@ -919,7 +975,6 @@ if (isset($first_time) && $first_time == 1 && api_is_allowed_to_edit(null, true)
 
                 $loadStats = [];
                 $teacher = api_is_allowed_to_edit(null, true);
-
                 if (!$teacher) {
                     if (api_get_setting('gradebook_detailed_admin_view') === 'true') {
                         $loadStats = [1, 2, 3];
@@ -997,7 +1052,7 @@ if (isset($first_time) && $first_time == 1 && api_is_allowed_to_edit(null, true)
                         $table.
                         $graph.
                         '<br />'.get_lang('Feedback').'<br />
-                        <textarea rows="5" cols="100" ></textarea>'
+                        <textarea rows="5" cols="100" >&nbsp;</textarea>'
                     );
                 } else {
                     echo $table;

+ 1 - 60
main/gradebook/lib/GradebookUtils.php

@@ -651,40 +651,6 @@ class GradebookUtils
         $current_value = $data;
     }
 
-    /**
-     * XML-parser: handle end of element.
-     */
-    public static function element_end($parser, $data)
-    {
-        global $user;
-        global $users;
-        global $current_value;
-        switch ($data) {
-            case 'Result':
-                $users[] = $user;
-                break;
-            default:
-                $user[$data] = $current_value;
-                break;
-        }
-    }
-
-    /**
-     * XML-parser: handle start of element.
-     */
-    public static function element_start($parser, $data)
-    {
-        global $user;
-        global $current_tag;
-        switch ($data) {
-            case 'Result':
-                $user = [];
-                break;
-            default:
-                $current_tag = $data;
-        }
-    }
-
     public static function overwritescore($resid, $importscore, $eval_max)
     {
         $result = Result::load($resid);
@@ -697,30 +663,6 @@ class GradebookUtils
         unset($result);
     }
 
-    /**
-     * Read the XML-file.
-     *
-     * @param string $file Path to the XML-file
-     *
-     * @return array All user information read from the file
-     */
-    public static function parse_xml_data($file)
-    {
-        global $current_tag;
-        global $current_value;
-        global $user;
-        global $users;
-        $users = [];
-        $parser = xml_parser_create();
-        xml_set_element_handler($parser, 'element_start', 'element_end');
-        xml_set_character_data_handler($parser, "character_data");
-        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
-        xml_parse($parser, file_get_contents($file));
-        xml_parser_free($parser);
-
-        return $users;
-    }
-
     /**
      * register user info about certificate.
      *
@@ -1602,7 +1544,6 @@ class GradebookUtils
         $userInfo = api_get_user_info($userId);
 
         $cat = $cats[0];
-
         $allcat = $cats[0]->get_subcategories(
             $userId,
             api_get_course_id(),
@@ -1666,7 +1607,7 @@ class GradebookUtils
             $table.
             $graph.
             '<br />'.get_lang('Feedback').'<br />
-            <textarea rows="5" cols="100" ></textarea>';
+            <textarea rows="5" cols="100">&nbsp;</textarea>';
 
         $result = $pdf->html_to_pdf_with_template(
             $content,

+ 31 - 1
main/gradebook/lib/be/category.class.php

@@ -388,7 +388,7 @@ class Category implements GradebookItem
 
         $courseInfo = api_get_course_info_by_id(api_get_course_int_id());
         $courseCode = $courseInfo['code'];
-        $session_id = intval($session_id);
+        $session_id = (int) $session_id;
 
         if (!empty($session_id)) {
             $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
@@ -940,6 +940,17 @@ class Category implements GradebookItem
         $course_code = '',
         $session_id = null
     ) {
+        $key = 'category:'.$this->id.'student:'.(int) $stud_id.'type:'.$type.'course:'.$course_code.'session:'.(int) $session_id;
+        $useCache = api_get_configuration_value('gradebook_use_apcu_cache');
+        $cacheAvailable = api_get_configuration_value('apc') && $useCache;
+
+        if ($cacheAvailable) {
+            $cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
+            if ($cacheDriver->contains($key)) {
+                return $cacheDriver->fetch($key);
+            }
+        }
+
         // Classic
         if (!empty($stud_id) && $type == '') {
             if (!empty($course_code)) {
@@ -1144,16 +1155,31 @@ class Category implements GradebookItem
         switch ($type) {
             case 'best':
                 if (empty($bestResult)) {
+                    if ($cacheAvailable) {
+                        $cacheDriver->save($key, null);
+                    }
+
                     return null;
                 }
+                if ($cacheAvailable) {
+                    $cacheDriver->save($key, [$bestResult, $weightsum]);
+                }
 
                 return [$bestResult, $weightsum];
                 break;
             case 'average':
                 if (empty($ressum)) {
+                    if ($cacheAvailable) {
+                        $cacheDriver->save($key, null);
+                    }
+
                     return null;
                 }
 
+                if ($cacheAvailable) {
+                    $cacheDriver->save($key, [$ressum, $weightsum]);
+                }
+
                 return [$ressum, $weightsum];
                 break;
             case 'ranking':
@@ -1164,6 +1190,10 @@ class Category implements GradebookItem
                 return AbstractLink::getCurrentUserRanking($stud_id, []);
                 break;
             default:
+                if ($cacheAvailable) {
+                    $cacheDriver->save($key, [$ressum, $weightsum]);
+                }
+
                 return [$ressum, $weightsum];
                 break;
         }

+ 2 - 2
main/gradebook/lib/be/evaluation.class.php

@@ -113,7 +113,7 @@ class Evaluation implements GradebookItem
      */
     public function setSessionId($sessionId)
     {
-        $this->sessionId = intval($sessionId);
+        $this->sessionId = (int) $sessionId;
     }
 
     public function get_date()
@@ -153,7 +153,7 @@ class Evaluation implements GradebookItem
 
     public function set_id($id)
     {
-        $this->id = $id;
+        $this->id = (int) $id;
     }
 
     public function set_name($name)

+ 75 - 15
main/gradebook/lib/be/exerciselink.class.php

@@ -113,7 +113,7 @@ class ExerciseLink extends AbstractLink
                     $attribute['id'] = $row['id'];
 
                     if (isset($attribute['path']) && is_array($attribute['path'])) {
-                        while (list($key, $path) = each($attribute['path'])) {
+                        foreach ($attribute['path'] as $path) {
                             $title = GetQuizName($path, $documentPath);
                             if ($title == '') {
                                 $title = basename($path);
@@ -186,20 +186,34 @@ class ExerciseLink extends AbstractLink
         $courseId = $this->getCourseId();
         $exerciseData = $this->get_exercise_data();
         $exerciseId = isset($exerciseData['id']) ? $exerciseData['id'] : 0;
+        $stud_id = (int) $stud_id;
 
         if (empty($exerciseId)) {
             return null;
         }
+
+        $key = 'exercise_link_id:'.
+            $this->get_id().
+            'exerciseId:'.$exerciseId.'student:'.$stud_id.'session:'.$sessionId.'courseId:'.$courseId.'type:'.$type;
+
+        $useCache = api_get_configuration_value('gradebook_use_apcu_cache');
+        $cacheAvailable = api_get_configuration_value('apc') && $useCache;
+        $cacheDriver = null;
+        if ($cacheAvailable) {
+            $cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
+            if ($cacheDriver->contains($key)) {
+                return $cacheDriver->fetch($key);
+            }
+        }
+
         $exercise = new Exercise($courseId);
         $exercise->read($exerciseData['id']);
 
-        $stud_id = (int) $stud_id;
-
         if (!$this->is_hp) {
             if ($exercise->exercise_was_added_in_lp == false) {
                 $sql = "SELECT * FROM $tblStats
                         WHERE
-                            exe_exo_id = ".$exerciseId." AND
+                            exe_exo_id = $exerciseId AND
                             orig_lp_id = 0 AND
                             orig_lp_item_id = 0 AND
                             status <> 'incomplete' AND
@@ -217,7 +231,7 @@ class ExerciseLink extends AbstractLink
                 $sql = "SELECT * 
                         FROM $tblStats
                         WHERE
-                            exe_exo_id = ".$exerciseId." AND
+                            exe_exo_id = $exerciseId AND
                             orig_lp_id = $lpId AND
                             status <> 'incomplete' AND
                             session_id = $sessionId AND
@@ -246,8 +260,17 @@ class ExerciseLink extends AbstractLink
         if (isset($stud_id) && empty($type)) {
             // for 1 student
             if ($data = Database::fetch_array($scores)) {
-                return [$data['exe_result'], $data['exe_weighting']];
+                $result = [$data['exe_result'], $data['exe_weighting']];
+                if ($cacheAvailable) {
+                    $cacheDriver->save($key, $result);
+                }
+
+                return $result;
             } else {
+                if ($cacheAvailable) {
+                    $cacheDriver->save($key, null);
+                }
+
                 return null;
             }
         } else {
@@ -290,25 +313,55 @@ class ExerciseLink extends AbstractLink
             }
 
             if ($student_count == 0) {
+                if ($cacheAvailable) {
+                    $cacheDriver->save($key, null);
+                }
+
                 return null;
             } else {
                 switch ($type) {
                     case 'best':
-                        return [$bestResult, $weight];
+                        $result = [$bestResult, $weight];
+                        if ($cacheAvailable) {
+                            $cacheDriver->save($key, $result);
+                        }
+
+                        return $result;
                         break;
                     case 'average':
                         $count = count($this->getStudentList());
                         if (empty($count)) {
-                            return [0, $weight];
+                            $result = [0, $weight];
+                            if ($cacheAvailable) {
+                                $cacheDriver->save($key, $result);
+                        }
+
+                            return $result;
+                        }
+
+                        $result = [$sumResult / $count, $weight];
+
+                        if ($cacheAvailable) {
+                            $cacheDriver->save($key, $result);
                         }
 
-                        return [$sumResult / $count, $weight];
+                        return $result;
                         break;
                     case 'ranking':
-                        return AbstractLink::getCurrentUserRanking($stud_id, $students);
+                        $ranking = AbstractLink::getCurrentUserRanking($stud_id, $students);
+                        if ($cacheAvailable) {
+                            $cacheDriver->save($key, $ranking);
+                        }
+
+                        return $ranking;
                         break;
                     default:
-                        return [$sum, $student_count];
+                        $result = [$sum, $student_count];
+                        if ($cacheAvailable) {
+                            $cacheDriver->save($key, $result);
+                        }
+
+                        return $result;
                         break;
                 }
             }
@@ -334,10 +387,17 @@ class ExerciseLink extends AbstractLink
         $exerciseId = $data['id'];
         $path = isset($data['path']) ? $data['path'] : '';
 
-        $url = api_get_path(WEB_CODE_PATH).'gradebook/exercise_jump.php?path='.$path.'&session_id='.$sessionId.'&cidReq='.$this->get_course_code().'&gradebook=view&exerciseId='.$exerciseId.'&type='.$this->get_type();
-        if ((!api_is_allowed_to_edit() && $this->calc_score($user_id) == null) || $status_user != 1) {
-            $url .= '&doexercise='.$exerciseId;
-        }
+        $url = api_get_path(WEB_CODE_PATH).'gradebook/exercise_jump.php?'
+            .http_build_query(
+                [
+                    'path' => $path,
+                    'session_id' => $sessionId,
+                    'cidReq' => $this->get_course_code(),
+                    'gradebook' => 'view',
+                    'exerciseId' => $exerciseId,
+                    'type' => $this->get_type(),
+                ]
+            );
 
         return $url;
     }

+ 29 - 0
main/gradebook/lib/be/result.class.php

@@ -283,5 +283,34 @@ class Result
         $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_RESULT);
         $sql = 'DELETE FROM '.$table.' WHERE id = '.$this->id;
         Database::query($sql);
+        $allowMultipleAttempts = api_get_configuration_value('gradebook_multiple_evaluation_attempts');
+        if ($allowMultipleAttempts) {
+            $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_RESULT_ATTEMPT);
+            $sql = "DELETE FROM $table WHERE result_id = ".$this->id;
+            Database::query($sql);
+        }
+    }
+
+    /**
+     * Check if exists a result with its user and evaluation.
+     *
+     * @throws \Doctrine\ORM\Query\QueryException
+     *
+     * @return bool
+     */
+    public function exists()
+    {
+        $em = Database::getManager();
+
+        $result = $em
+            ->createQuery(
+                'SELECT COUNT(gr) FROM ChamiloCoreBundle:GradebookResult gr
+                WHERE gr.evaluationId = :eval_id AND gr.userId = :user_id'
+            )
+            ->setParameters(['eval_id' => $this->evaluation, 'user_id' => $this->user_id])
+            ->getSingleScalarResult();
+        $count = (int) $result;
+
+        return $count > 0;
     }
 }

+ 9 - 6
main/gradebook/lib/fe/dataform.class.php

@@ -107,13 +107,16 @@ class DataForm extends FormValidator
         $this->addElement('hidden', 'formSent');
         $this->addElement('header', get_lang('ImportFileLocation'));
         $this->addElement('file', 'import_file', get_lang('Location'));
-        $allowed_file_types = [
-            'xml',
-            'csv',
-        ];
+        $this->addElement(
+            'radio',
+            'file_type',
+            get_lang('FileType'),
+            'CSV (<a href="docs/example_csv.html" target="_blank" download>'
+                .get_lang('ExampleCSVFile')
+                .'</a>)',
+            'csv'
+        );
         //$this->addRule('file', get_lang('InvalidExtension') . ' (' . implode(',', $allowed_file_types) . ')', 'filetype', $allowed_file_types);
-        $this->addElement('radio', 'file_type', get_lang('FileType'), 'CSV (<a href="docs/example_csv.html" target="_blank">'.get_lang('ExampleCSVFile').'</a>)', 'csv');
-        $this->addElement('radio', 'file_type', null, 'XML (<a href="docs/example_xml.html" target="_blank">'.get_lang('ExampleXMLFile').'</a>)', 'xml');
         $this->addElement('checkbox', 'overwrite', null, get_lang('OverwriteScores'));
         $this->addElement('checkbox', 'ignoreerrors', null, get_lang('IgnoreErrors'));
         $this->addButtonImport(get_lang('Ok'));

+ 12 - 9
main/gradebook/lib/fe/displaygradebook.php

@@ -11,7 +11,7 @@ class DisplayGradebook
     /**
      * Displays the header for the result page containing the navigation tree and links.
      *
-     * @param $evalobj
+     * @param Evaluation $evalobj
      * @param $selectcat
      * @param $shownavbar 1=show navigation bar
      * @param $forpdf only output for pdf file
@@ -74,6 +74,17 @@ class DisplayGradebook
                     'h3',
                     get_lang('Score').': '.$scoredisplay->display_score($student_score, SCORE_DIV_PERCENT)
                 );
+
+                $allowMultipleAttempts = api_get_configuration_value('gradebook_multiple_evaluation_attempts');
+                if ($allowMultipleAttempts) {
+                    $results = Result::load(null, api_get_user_id(), $evalobj->get_id());
+                    if (!empty($results)) {
+                        /** @var Result $resultData */
+                        foreach ($results as $resultData) {
+                            $student_score .= ResultTable::getResultAttemptTable($resultData);
+                        }
+                    }
+                }
             }
         }
         $description = '';
@@ -100,15 +111,7 @@ class DisplayGradebook
         if (!$evalobj->has_results()) {
             $evalinfo .= '<br /><i>'.get_lang('NoResultsInEvaluation').'</i>';
         }
-        /* Code comment without reason
-        elseif ($scoredisplay->is_custom() && api_get_self() != '/main/gradebook/gradebook_statistics.php') {
-            if (api_is_allowed_to_edit(null, true)) {
-                if ($page != 'statistics') {
                     //$evalinfo .= '<br /><br /><a href="gradebook_view_result.php?selecteval='.Security::remove_XSS($_GET['selecteval']).'"> '.Display::return_icon(('evaluation_rate.png'),get_lang('ViewResult'),'',ICON_SIZE_MEDIUM) . '</a>';
-                }
-            }
-        }
-        */
         if ($page != 'statistics') {
             if (api_is_allowed_to_edit(null, true)) {
                 $evalinfo .= '<br /><a href="gradebook_statistics.php?'.api_get_cidreq().'&selecteval='.Security::remove_XSS($_GET['selecteval']).'"> '.

+ 6 - 1
main/gradebook/lib/fe/evalform.class.php

@@ -20,7 +20,7 @@ class EvalForm extends FormValidator
     const TYPE_ALL_RESULTS_EDIT = 6;
     const TYPE_ADD_USERS_TO_EVAL = 7;
 
-    private $evaluation_object;
+    protected $evaluation_object;
     private $result_object;
     private $extra;
 
@@ -395,6 +395,11 @@ class EvalForm extends FormValidator
             $this->evaluation_object->get_max()
         );
 
+        $allowMultipleAttempts = api_get_configuration_value('gradebook_multiple_evaluation_attempts');
+        if ($allowMultipleAttempts) {
+            $this->addTextarea('comment', get_lang('Comment'));
+        }
+
         $this->addButtonSave(get_lang('Edit'), 'submit');
         $this->addElement('hidden', 'hid_user_id', $this->result_object->get_user_id());
     }

+ 6 - 2
main/gradebook/lib/fe/flatviewtable.class.php

@@ -187,7 +187,11 @@ class FlatViewTable extends SortableTable
             $dataSet = new pData();
             $dataSet->addPoints($resource, 'Serie');
             $dataSet->addPoints(array_keys($resource), 'Labels');
-            $dataSet->setSerieDescription('Labels', strip_tags($headerName[$i - 1]));
+            $header = $headerName[$i - 1];
+            if (is_array($header) && isset($header['header'])) {
+                $header = $header['header'];
+            }
+            $dataSet->setSerieDescription('Labels', strip_tags($header));
             $dataSet->setAbscissa('Labels');
             $dataSet->setAbscissaName(get_lang('GradebookSkillsRanking'));
             $dataSet->setAxisName(0, get_lang('Students'));
@@ -245,7 +249,7 @@ class FlatViewTable extends SortableTable
                 $myPicture->drawText(
                     250,
                     30,
-                    strip_tags($headerName[$i - 1]),
+                    strip_tags($header),
                     [
                         'FontSize' => 12,
                         'Align' => TEXT_ALIGN_BOTTOMMIDDLE,

+ 46 - 23
main/gradebook/lib/fe/gradebooktable.class.php

@@ -79,6 +79,7 @@ class GradebookTable extends SortableTable
         $this->cats = $cats;
         $this->loadStats = $loadStats;
         $this->datagen = new GradebookDataGenerator($cats, $evals, $links);
+        $this->datagen->hidePercentage = api_get_configuration_value('hide_gradebook_percentage_user_result');
 
         if (!empty($userId)) {
             $this->datagen->userId = $userId;
@@ -104,16 +105,17 @@ class GradebookTable extends SortableTable
 
         $model = ExerciseLib::getCourseScoreModel();
 
-        if ($this->teacherView) {
             $this->set_header(
                 $column++,
                 get_lang('Weight'),
                 '',
                 'width="100px"'
             );
-        } else {
-            $this->set_header($column++, get_lang('Weight'), false);
+
+        if (!$this->teacherView) {
             $this->set_header($column++, get_lang('Result'), false);
+        }
+
             if (empty($model)) {
                 if (in_array(1, $this->loadStats)) {
                     $this->set_header($column++, get_lang('Ranking'), false);
@@ -126,6 +128,8 @@ class GradebookTable extends SortableTable
                 }
             }
 
+        if ($this->teacherView) {
+        } else {
             if (!empty($cats)) {
                 if ($this->exportToPdf == false) {
                     $this->set_header($column++, get_lang('Actions'), false);
@@ -285,7 +289,9 @@ class GradebookTable extends SortableTable
         }
 
         $model = ExerciseLib::getCourseScoreModel();
-
+        $userExerciseScoreInCategory = api_get_configuration_value(
+            'gradebook_use_exercise_score_settings_in_categories'
+        );
         // Categories.
         if (!empty($data_array)) {
             foreach ($data_array as $data) {
@@ -320,7 +326,6 @@ class GradebookTable extends SortableTable
                 }
 
                 $this->dataForGraph['categories'][] = $item->get_name();
-
                 $main_categories[$item->get_id()]['weight'] = $item->get_weight();
                 $total_categories_weight += $item->get_weight();
 
@@ -411,17 +416,32 @@ class GradebookTable extends SortableTable
                                 $data['result_score'][1]
                             );
                         }
-                        $totalResultAverageValue = strip_tags(
-                            $scoredisplay->display_score(
-                                $totalResult,
-                                SCORE_AVERAGE
-                            )
+
+                        $mode = SCORE_AVERAGE;
+                        if ($userExerciseScoreInCategory) {
+                            $mode = SCORE_SIMPLE;
+
+                            $result = ExerciseLib::convertScoreToPlatformSetting($totalAverage[0], $totalAverage[1]);
+                            $totalAverage[0] = $result['score'];
+                            $totalAverage[1] = $result['weight'];
+
+                            $result = ExerciseLib::convertScoreToPlatformSetting($totalResult[0], $totalResult[1]);
+                            $totalResult[0] = $result['score'];
+                            $totalResult[1] = $result['weight'];
+
+                            $result = ExerciseLib::convertScoreToPlatformSetting(
+                                $data['result_score'][0],
+                                $data['result_score'][1]
                         );
+                            $data['my_result_no_float'][0] = $result['score'];
+                        }
+
+                        $totalResultAverageValue = strip_tags($scoredisplay->display_score($totalResult, $mode));
+                        $totalAverageValue = strip_tags($scoredisplay->display_score($totalAverage, $mode));
 
                         $this->dataForGraph['my_result'][] = floatval($totalResultAverageValue);
-                        $this->dataForGraph['my_result_no_float'][] = $data['result_score'][0];
-                        $totalAverageValue = strip_tags($scoredisplay->display_score($totalAverage, SCORE_AVERAGE));
                         $this->dataForGraph['average'][] = floatval($totalAverageValue);
+                        $this->dataForGraph['my_result_no_float'][] = $data['result_score'][0];
 
                         if (empty($model)) {
                             // Ranking
@@ -447,7 +467,6 @@ class GradebookTable extends SortableTable
                         }
                     } else {
                         $row[] = $scoreToDisplay;
-
                         if (!empty($this->cats)) {
                             if ($this->exportToPdf == false) {
                                 $row[] = $this->build_edit_column($item);
@@ -557,6 +576,7 @@ class GradebookTable extends SortableTable
                                     if (!is_null($value_data)) {
                                         // 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;
@@ -699,8 +719,14 @@ class GradebookTable extends SortableTable
                     $totalRanking = [];
                     $invalidateRanking = true;
                     $average = 0;
+                    $main_cat[0]->setStudentList($this->studentList);
                     foreach ($this->studentList as $student) {
-                        $score = $main_cat[0]->calc_score($student['user_id']);
+                        $score = $main_cat[0]->calc_score(
+                            $student['user_id'],
+                            null,
+                            api_get_course_id(),
+                            api_get_session_id()
+                        );
                         if (!empty($score[0])) {
                             $invalidateRanking = false;
                         }
@@ -709,7 +735,6 @@ class GradebookTable extends SortableTable
                     }
 
                     $totalRanking = AbstractLink::getCurrentUserRanking($user_id, $totalRanking);
-
                     $totalRanking = $scoredisplay->display_score(
                             $totalRanking,
                             SCORE_DIV,
@@ -720,13 +745,12 @@ class GradebookTable extends SortableTable
                     if ($invalidateRanking) {
                         $totalRanking = null;
                     }
-
                     $row[] = $totalRanking;
                 }
+
                 if (in_array(2, $this->loadStats)) {
                     // Overwrite main weight
                     $totalBest[1] = $main_weight;
-
                     $totalBest = $scoredisplay->display_score(
                             $totalBest,
                             SCORE_DIV,
@@ -739,7 +763,6 @@ class GradebookTable extends SortableTable
                     // Overwrite main weight
                     $totalAverage[0] = $average / count($this->studentList);
                     $totalAverage[1] = $main_weight;
-
                     $totalAverage = $scoredisplay->display_score(
                             $totalAverage,
                             SCORE_DIV,
@@ -749,8 +772,9 @@ class GradebookTable extends SortableTable
 
                     $row[] = $totalAverage;
                 }
-
-                $sortable_data[] = $row;
+                if (!empty($row)) {
+                    $sortable_data[] = $row;
+                }
             }
         }
 
@@ -880,8 +904,7 @@ class GradebookTable extends SortableTable
             // $data['average'] = array(rand(0,50), rand(0,50));
             $dataSet->addPoints($data['average'], get_lang('Average'));
             $dataSet->addPoints($data['categories'], 'categories');
-
-            $dataSet->setAbscissa("categories");
+            $dataSet->setAbscissa('categories');
             $xSize = 600;
             $ySize = 400;
             $pChart = new pImage($xSize, $ySize, $dataSet);
@@ -959,7 +982,7 @@ class GradebookTable extends SortableTable
             $imgWebPath = api_get_path(WEB_ARCHIVE_PATH).$chartHash;
 
             if (file_exists($imgSysPath)) {
-                $result = '<div id="contentArea" style="text-align: center;" >';
+                $result = '<br /><div id="contentArea" style="text-align: center;" >';
                 $result .= '<img src="'.$imgWebPath.'" >';
                 $result .= '</div>';
 

+ 65 - 2
main/gradebook/lib/fe/resulttable.class.php

@@ -164,6 +164,54 @@ class ResultTable extends SortableTable
         return $sortable_data;
     }
 
+    /**
+     * @param Result $result
+     * @param string $url
+     *
+     * @return string
+     */
+    public static function getResultAttemptTable($result, $url = '')
+    {
+        if (empty($result)) {
+            return '';
+        }
+
+        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_RESULT_ATTEMPT);
+
+        $sql = "SELECT * FROM $table WHERE result_id = ".$result->get_id().' ORDER BY created_at DESC';
+        $resultQuery = Database::query($sql);
+        $list = Database::store_result($resultQuery);
+
+        $htmlTable = new HTML_Table(['class' => 'data_table']);
+        $htmlTable->setHeaderContents(0, 0, get_lang('Score'));
+        $htmlTable->setHeaderContents(0, 1, get_lang('Comment'));
+        $htmlTable->setHeaderContents(0, 2, get_lang('CreatedAt'));
+
+        if (!empty($url)) {
+            $htmlTable->setHeaderContents(0, 3, get_lang('Actions'));
+        }
+
+        $row = 1;
+        foreach ($list as $data) {
+            $htmlTable->setCellContents($row, 0, $data['score']);
+            $htmlTable->setCellContents($row, 1, $data['comment']);
+            $htmlTable->setCellContents($row, 2, Display::dateToStringAgoAndLongDate($data['created_at']));
+            if (!empty($url)) {
+                $htmlTable->setCellContents(
+                    $row,
+                    3,
+                    Display::url(
+                        Display::return_icon('delete.png', get_lang('Delete')),
+                        $url.'&action=delete_attempt&result_attempt_id='.$data['id']
+                    )
+                );
+            }
+            $row++;
+        }
+
+        return $htmlTable->toHtml();
+    }
+
     /**
      * @param array $item
      *
@@ -172,10 +220,25 @@ class ResultTable extends SortableTable
     private function build_edit_column($item)
     {
         $locked_status = $this->evaluation->get_locked();
+        $allowMultipleAttempts = api_get_configuration_value('gradebook_multiple_evaluation_attempts');
+        $baseUrl = api_get_self().'?selecteval='.$this->evaluation->get_id().'&'.api_get_cidreq();
         if (api_is_allowed_to_edit(null, true) && $locked_status == 0) {
-            //api_is_course_admin()
-            $edit_column = '<a href="'.api_get_self().'?editres='.$item['result_id'].'&selecteval='.$this->evaluation->get_id().'&'.api_get_cidreq().'">'.
+            $edit_column = '';
+            if ($allowMultipleAttempts) {
+                if (!empty($item['percentage_score'])) {
+                    $edit_column .=
+                        Display::url(
+                            Display::return_icon('add.png', get_lang('AddAttempt'), '', '22'),
+                            $baseUrl.'&action=add_attempt&editres='.$item['result_id']
+                        );
+                } else {
+                    $edit_column .= '<a href="'.api_get_self().'?editres='.$item['result_id'].'&selecteval='.$this->evaluation->get_id().'&'.api_get_cidreq().'">'.
                 Display::return_icon('edit.png', get_lang('Modify'), '', '22').'</a>';
+                }
+            } else {
+                $edit_column .= '<a href="'.api_get_self().'?editres='.$item['result_id'].'&selecteval='.$this->evaluation->get_id().'&'.api_get_cidreq().'">'.
+                    Display::return_icon('edit.png', get_lang('Modify'), '', '22').'</a>';
+            }
             $edit_column .= ' <a href="'.api_get_self().'?delete_mark='.$item['result_id'].'&selecteval='.$this->evaluation->get_id().'&'.api_get_cidreq().'">'.
                 Display::return_icon('delete.png', get_lang('Delete'), '', '22').'</a>';
         }

+ 2 - 1
main/gradebook/lib/gradebook_data_generator.class.php

@@ -22,6 +22,7 @@ class GradebookDataGenerator
     const GDG_SORT_DESC = 64;
     const GDG_SORT_ID = 128;
     public $userId;
+    public $hidePercentage = false;
 
     private $items;
     private $evals_links;
@@ -141,6 +142,7 @@ class GradebookDataGenerator
 
                 // Best
                 $best = $this->buildBestResultColumn($item);
+
                 $row['best'] = $best['display'];
                 $row['best_score'] = $best['score'];
 
@@ -188,7 +190,6 @@ class GradebookDataGenerator
                             $ignore_score_color,
                             true
                         );
-
                         if (!empty($score['score'][0])) {
                             $invalidateResults = false;
                         }

+ 10 - 1
main/gradebook/my_certificates.php

@@ -12,6 +12,15 @@ $cidReset = true;
 
 require_once __DIR__.'/../inc/global.inc.php';
 
+$logInfo = [
+    'tool' => 'MyCertificates',
+    'tool_id' => 0,
+    'tool_id_detail' => 0,
+    'action' => '',
+    'action_details' => '',
+];
+Event::registerLog($logInfo);
+
 if (api_is_anonymous()) {
     api_not_allowed(true);
 }
@@ -39,7 +48,7 @@ if (api_get_setting('allow_public_certificates') === 'true') {
         'actions',
         Display::toolbarButton(
             get_lang('SearchCertificates'),
-            api_get_path(WEB_CODE_PATH)."gradebook/search.php",
+            api_get_path(WEB_CODE_PATH).'gradebook/search.php',
             'search',
             'info'
         )

+ 155 - 0
main/inc/ajax/announcement.ajax.php

@@ -23,6 +23,161 @@ if (!empty($groupId)) {
 }
 
 switch ($action) {
+    case 'preview':
+        $allowToEdit = (
+            api_is_allowed_to_edit(false, true) ||
+            (api_get_course_setting('allow_user_edit_announcement') && !api_is_anonymous())
+        );
+
+        $drhHasAccessToSessionContent = api_drh_can_access_all_session_content();
+        if (!empty($sessionId) && $drhHasAccessToSessionContent) {
+            $allowToEdit = $allowToEdit || api_is_drh();
+        }
+
+        if ($allowToEdit === false && !empty($groupId)) {
+            $groupProperties = GroupManager:: get_group_properties($groupId);
+            // Check if user is tutor group
+            $isTutor = GroupManager::is_tutor_of_group(api_get_user_id(), $groupProperties, $courseId);
+            if ($isTutor) {
+                $allowToEdit = true;
+            }
+        }
+
+        if ($allowToEdit === false) {
+            exit;
+        }
+
+        $users = isset($_REQUEST['users']) ? json_decode($_REQUEST['users']) : '';
+        $formParams = [];
+        if (isset($_REQUEST['form'])) {
+            parse_str($_REQUEST['form'], $formParams);
+        }
+
+        $previewGroups = [];
+        $previewUsers = [];
+        $previewTotal = [];
+        if (empty($groupId)) {
+            if (empty($users) ||
+                (!empty($users) && isset($users[0]) && $users[0] == 'everyone')
+            ) {
+                // All users in course session
+                if (empty($sessionId)) {
+                    $students = CourseManager::get_user_list_from_course_code($courseInfo['code']);
+                } else {
+                    $students = CourseManager::get_user_list_from_course_code($courseInfo['code'], $sessionId);
+                }
+                foreach ($students as $student) {
+                    $previewUsers[] = $student['user_id'];
+                }
+
+                $groupList = GroupManager::get_group_list(null, $courseInfo, null, $sessionId);
+                foreach ($groupList as $group) {
+                    $previewGroups[] = $group['iid'];
+                }
+            } else {
+                $send_to = CourseManager::separateUsersGroups($users);
+                // Storing the selected groups
+                if (is_array($send_to['groups']) &&
+                    !empty($send_to['groups'])
+                ) {
+                    $counter = 1;
+                    foreach ($send_to['groups'] as $group) {
+                        $previewGroups[] = $group;
+                    }
+                }
+
+                // Storing the selected users
+                if (is_array($send_to['users'])) {
+                    $counter = 1;
+                    foreach ($send_to['users'] as $user) {
+                        $previewUsers[] = $user;
+                    }
+                }
+            }
+        } else {
+            $send_to_users = CourseManager::separateUsersGroups($users);
+            $sentToAllGroup = false;
+            if (empty($send_to_users['groups']) && empty($send_to_users['users'])) {
+                $previewGroups[] = $groupId;
+                $sentToAllGroup = true;
+            }
+
+            if ($sentToAllGroup === false) {
+                if (!empty($send_to_users['groups'])) {
+                    foreach ($send_to_users['groups'] as $group) {
+                        $previewGroups[] = $group;
+                    }
+                }
+
+                if (!empty($send_to_users['users'])) {
+                    foreach ($send_to_users['users'] as $user) {
+                        $previewUsers[] = $user;
+                    }
+                }
+            }
+        }
+
+        if (isset($formParams['send_to_users_in_session']) && $formParams['send_to_users_in_session'] == 1) {
+            $sessionList = SessionManager::get_session_by_course(api_get_course_int_id());
+
+            if (!empty($sessionList)) {
+                foreach ($sessionList as $sessionInfo) {
+                    $sessionId = $sessionInfo['id'];
+                    $userList = CourseManager::get_user_list_from_course_code(
+                        $courseCode,
+                        $sessionId
+                    );
+
+                    if (!empty($userList)) {
+                        foreach ($userList as $user) {
+                            $previewUsers[] = $user;
+                        }
+                    }
+                }
+            }
+        }
+
+        if (isset($formParams['send_to_hrm_users']) && $formParams['send_to_hrm_users'] == 1) {
+            foreach ($previewUsers as $userId) {
+                $userInfo = api_get_user_info($userId);
+                $drhList = UserManager::getDrhListFromUser($userId);
+                if (!empty($drhList)) {
+                    foreach ($drhList as $drhInfo) {
+                        $previewUsers[] = $drhInfo['id'];
+                    }
+                }
+            }
+        }
+
+        if (isset($formParams['send_me_a_copy_by_email']) && $formParams['send_me_a_copy_by_email'] == 1) {
+            $previewUsers[] = api_get_user_id();
+        }
+
+        $previewUserNames = [];
+        $previewGroupNames = [];
+
+        if (!empty($previewGroups)) {
+            $previewGroups = array_unique($previewGroups);
+            foreach ($previewGroups as $groupId) {
+                $groupInfo = GroupManager::get_group_properties($groupId);
+                $previewGroupNames[] = Display::label($groupInfo['name'], 'info');
+            }
+            $previewTotal = $previewGroupNames;
+        }
+
+        if (!empty($previewUsers)) {
+            $previewUsers = array_unique($previewUsers);
+            foreach ($previewUsers as $userId) {
+                $userInfo = api_get_user_info($userId);
+                $previewUserNames[] = Display::label($userInfo['complete_name']);
+            }
+            $previewTotal = array_merge($previewTotal, $previewUserNames);
+        }
+
+        $previewTotal = array_map(function ($value) { return ''.$value; }, $previewTotal);
+
+        echo json_encode($previewTotal);
+        break;
     case 'delete_item':
         if ($isAllowedToEdit) {
             if (empty($_REQUEST['id'])) {

+ 27 - 1
main/inc/lib/AnnouncementEmail.php

@@ -274,12 +274,32 @@ class AnnouncementEmail
         return $result;
     }
 
+    /**
+     * Send announcement by email to myself.
+     */
+    public function sendAnnouncementEmailToMySelf()
+    {
+        $userId = api_get_user_id();
+        $subject = $this->subject();
+        $message = $this->message($userId);
+        MessageManager::send_message_simple(
+            $userId,
+            $subject,
+            $message,
+            api_get_user_id(),
+            false,
+            true
+        );
+    }
+
     /**
      * Send emails to users.
      *
      * @param bool $sendToUsersInSession
      * @param bool $sendToDrhUsers       send a copy of the message to the DRH users
      * @param int  $senderId             related to the main user
+     *
+     * @return array
      */
     public function send($sendToUsersInSession = false, $sendToDrhUsers = false, $senderId = 0)
     {
@@ -296,7 +316,7 @@ class AnnouncementEmail
         if (empty($users) && !empty($this->logger)) {
             $this->logger->addInfo('User list is empty. No emails will be sent.');
         }
-
+        $messageSentTo = [];
         foreach ($users as $user) {
             $message = $this->message($user['user_id']);
             $wasSent = MessageManager::messageWasAlreadySent($senderId, $user['user_id'], $subject, $message);
@@ -306,6 +326,8 @@ class AnnouncementEmail
                         'Announcement: #'.$this->announcement('id').'. Send email to user: #'.$user['user_id']
                     );
                 }
+
+                $messageSentTo[] = $user['user_id'];
                 MessageManager::send_message_simple(
                     $user['user_id'],
                     $subject,
@@ -342,6 +364,7 @@ class AnnouncementEmail
                     );
                     if (!empty($userList)) {
                         foreach ($userList as $user) {
+                            $messageSentTo[] = $user['user_id'];
                             MessageManager::send_message_simple(
                                 $user['user_id'],
                                 $subject,
@@ -357,6 +380,9 @@ class AnnouncementEmail
         }
 
         $this->logMailSent();
+        $messageSentTo = array_unique($messageSentTo);
+
+        return $messageSentTo;
     }
 
     /**

+ 180 - 57
main/inc/lib/AnnouncementManager.php

@@ -1,6 +1,7 @@
 <?php
 /* For licensing terms, see /license.txt */
 
+use Chamilo\CoreBundle\Entity\ExtraFieldValues;
 use Chamilo\CourseBundle\Entity\CAnnouncement;
 use Chamilo\CourseBundle\Entity\CItemProperty;
 
@@ -29,6 +30,7 @@ class AnnouncementManager
     {
         $tags = [
             '((user_name))',
+            '((user_email))',
             '((user_firstname))',
             '((user_lastname))',
             '((user_official_code))',
@@ -93,8 +95,10 @@ class AnnouncementManager
         $data['user_firstname'] = '';
         $data['user_lastname'] = '';
         $data['user_official_code'] = '';
+        $data['user_email'] = '';
         if (!empty($readerInfo)) {
             $data['user_name'] = $readerInfo['username'];
+            $data['user_email'] = $readerInfo['email'];
             $data['user_firstname'] = $readerInfo['firstname'];
             $data['user_lastname'] = $readerInfo['lastname'];
             $data['user_official_code'] = $readerInfo['official_code'];
@@ -110,7 +114,7 @@ class AnnouncementManager
             $extraFields = $extraField->get_all(['filter = ?' => 1]);
             if (!empty($extraFields)) {
                 foreach ($extraFields as $extra) {
-                    $data["extra_".$extra['variable']] = '';
+                    $data['extra_'.$extra['variable']] = '';
                 }
             }
 
@@ -119,7 +123,12 @@ class AnnouncementManager
                     if (isset($extra['value'])) {
                         /** @var \Chamilo\CoreBundle\Entity\ExtraFieldValues $value */
                         $value = $extra['value'];
-                        $data['extra_'.$value->getField()->getVariable()] = $value->getValue();
+                        if ($value instanceof ExtraFieldValues) {
+                            $field = $value->getField();
+                            if ($field) {
+                                $data['extra_'.$field->getVariable()] = $value->getValue();
+                            }
+                        }
                     }
                 }
             }
@@ -457,10 +466,10 @@ class AnnouncementManager
         $allow = !api_get_configuration_value('hide_announcement_sent_to_users_info');
         if (api_is_allowed_to_edit(false, true) && $allow) {
             $sent_to = self::sent_to('announcement', $id);
-            $sent_to_form = self::sent_to_form($sent_to);
+            $sentToForm = self::sent_to_form($sent_to);
             $html .= Display::tag(
                 'td',
-                get_lang('SentTo').': '.$sent_to_form,
+                get_lang('SentTo').': '.$sentToForm,
                 ['class' => 'announcements_datum']
             );
         }
@@ -564,7 +573,6 @@ class AnnouncementManager
 
         $courseId = $courseInfo['real_id'];
         $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
-
         $authorId = empty($authorId) ? api_get_user_id() : $authorId;
 
         if (empty($end_date)) {
@@ -676,20 +684,20 @@ class AnnouncementManager
     }
 
     /**
-     * @param $title
-     * @param $newContent
-     * @param $to
-     * @param $to_users
+     * @param string $title
+     * @param string $newContent
+     * @param int    $groupId
+     * @param array  $to_users
      * @param array  $file
      * @param string $file_comment
      * @param bool   $sendToUsersInSession
      *
      * @return bool|int
      */
-    public static function add_group_announcement(
+    public static function addGroupAnnouncement(
         $title,
         $newContent,
-        $to,
+        $groupId,
         $to_users,
         $file = [],
         $file_comment = '',
@@ -730,14 +738,26 @@ class AnnouncementManager
                 );
             }
 
+            $send_to_users = CourseManager::separateUsersGroups($to_users);
             // Store in item_property (first the groups, then the users)
             //if (!isset($to_users)) {
-            if (isset($to_users[0]) && $to_users[0] === 'everyone') {
+            $sentToAllGroup = false;
+            if (empty($send_to_users['groups']) && empty($send_to_users['users'])) {
+                $groupInfo = GroupManager::get_group_properties($groupId);
+                api_item_property_update(
+                    $courseInfo,
+                    TOOL_ANNOUNCEMENT,
+                    $last_id,
+                    'AnnouncementAdded',
+                    api_get_user_id(),
+                    $groupInfo
+                );
+                $sentToAllGroup = true;
+            }
                 // when no user is selected we send it to everyone
-                $send_to = CourseManager::separateUsersGroups($to);
-                // storing the selected groups
-                if (is_array($send_to['groups'])) {
-                    foreach ($send_to['groups'] as $group) {
+            if ($sentToAllGroup === false) {
+                if (!empty($send_to_users['groups'])) {
+                    foreach ($send_to_users['groups'] as $group) {
                         $groupInfo = GroupManager::get_group_properties($group);
                         api_item_property_update(
                             $courseInfo,
@@ -749,16 +769,10 @@ class AnnouncementManager
                         );
                     }
                 }
-            } else {
-                $send_to_groups = CourseManager::separateUsersGroups($to);
-                $send_to_users = CourseManager::separateUsersGroups($to_users);
-                $to_groups = $send_to_groups['groups'];
-                $to_users = $send_to_users['users'];
                 // storing the selected users
-                if (is_array($to_users) && is_array($to_groups)) {
-                    foreach ($to_groups as $group) {
-                        $groupInfo = GroupManager::get_group_properties($group);
-                        foreach ($to_users as $user) {
+                $groupInfo = GroupManager::get_group_properties($groupId);
+                if (!empty($send_to_users['users'])) {
+                    foreach ($send_to_users['users'] as $user) {
                             api_item_property_update(
                                 $courseInfo,
                                 TOOL_ANNOUNCEMENT,
@@ -768,7 +782,6 @@ class AnnouncementManager
                                 $groupInfo,
                                 $user
                             );
-                        }
                     }
                 }
             }
@@ -940,7 +953,7 @@ class AnnouncementManager
                             $courseInfo,
                             TOOL_ANNOUNCEMENT,
                             $announcementId,
-                            "AnnouncementUpdated",
+                            'AnnouncementUpdated',
                             api_get_user_id(),
                             0,
                             $user['user_id'],
@@ -1065,6 +1078,7 @@ class AnnouncementManager
                     ip.tool='announcement' AND
                     announcement.id = $id
                 ";
+
         $result = Database::query($sql);
         if (Database::num_rows($result)) {
             return Database::fetch_array($result);
@@ -1103,39 +1117,62 @@ class AnnouncementManager
      *
      * @param string $tool
      * @param int    $id
+     * @param bool   $includeGroupWhenLoadingUser
      *
      * @return array
      */
-    public static function load_edit_users($tool, $id)
+    public static function loadEditUsers($tool, $id, $includeGroupWhenLoadingUser = false)
     {
         $table = Database::get_course_table(TABLE_ITEM_PROPERTY);
         $tool = Database::escape_string($tool);
         $id = (int) $id;
         $courseId = api_get_course_int_id();
+        $groupId = api_get_group_id();
 
         $sql = "SELECT to_user_id, to_group_id FROM $table
                 WHERE c_id = $courseId AND tool='$tool' AND ref = $id";
+
         $result = Database::query($sql);
         $to = [];
         while ($row = Database::fetch_array($result)) {
             // This is the iid of c_group_info
             $toGroup = $row['to_group_id'];
+            if (empty($row['to_user_id']) && !empty($groupId) && $groupId != $toGroup) {
+                //continue;
+            }
             switch ($toGroup) {
                 // it was send to one specific user
                 case null:
                     if (isset($row['to_user_id']) && !empty($row['to_user_id'])) {
+                        if (!in_array('USER:'.$row['to_user_id'], $to)) {
                         $to[] = 'USER:'.$row['to_user_id'];
                     }
+                    }
                     break;
                 // it was sent to everyone
                 case 0:
                     return 'everyone';
                     break;
                 default:
+                    if (isset($row['to_user_id']) && !empty($row['to_user_id'])) {
+                        if (!in_array('USER:'.$row['to_user_id'], $to)) {
+                            $to[] = 'USER:'.$row['to_user_id'];
+                        }
+                    } else {
+                        if (!in_array('GROUP:'.$toGroup, $to)) {
                     $to[] = 'GROUP:'.$toGroup;
             }
         }
 
+                    if ($includeGroupWhenLoadingUser) {
+                        if (!in_array('GROUP:'.$toGroup, $to)) {
+                            $to[] = 'GROUP:'.$toGroup;
+                        }
+                    }
+                    break;
+            }
+        }
+
         return $to;
     }
 
@@ -1160,16 +1197,15 @@ class AnnouncementManager
         $group_names = self::get_course_groups();
 
         // we count the number of users and the number of groups
+        $number_users = 0;
         if (isset($sent_to_array['users'])) {
             $number_users = count($sent_to_array['users']);
-        } else {
-            $number_users = 0;
         }
+        $number_groups = 0;
         if (isset($sent_to_array['groups'])) {
             $number_groups = count($sent_to_array['groups']);
-        } else {
-            $number_groups = 0;
         }
+
         $total_numbers = $number_users + $number_groups;
 
         // starting the form if there is more than one user/group
@@ -1178,33 +1214,52 @@ class AnnouncementManager
             // outputting the name of the groups
             if (is_array($sent_to_array['groups'])) {
                 foreach ($sent_to_array['groups'] as $group_id) {
-                    $output[] = $group_names[$group_id]['name'];
+                    $users = GroupManager::getStudents($group_id, true);
+                    $userToArray = [];
+                    foreach ($users as $student) {
+                        $userToArray[] = $student['complete_name_with_username'];
+                    }
+                    $output[] =
+                        '<br />'.
+                        Display::label($group_names[$group_id]['name'], 'info').
+                        '&nbsp;'.implode(', ', $userToArray);
                 }
             }
 
             if (isset($sent_to_array['users'])) {
                 if (is_array($sent_to_array['users'])) {
+                    $usersToArray = [];
                     foreach ($sent_to_array['users'] as $user_id) {
                         $user_info = api_get_user_info($user_id);
-                        $output[] = $user_info['complete_name_with_username'];
+                        $usersToArray[] = $user_info['complete_name_with_username'];
                     }
+                    $output[] = '<br />'.Display::label(get_lang('Users')).'&nbsp;'.implode(', ', $usersToArray);
                 }
             }
         } else {
             // there is only one user/group
-            if (isset($sent_to_array['users']) and is_array($sent_to_array['users'])) {
+            if (isset($sent_to_array['users']) && is_array($sent_to_array['users'])) {
                 $user_info = api_get_user_info($sent_to_array['users'][0]);
                 $output[] = api_get_person_name($user_info['firstname'], $user_info['lastname']);
             }
-            if (isset($sent_to_array['groups']) and
-                is_array($sent_to_array['groups']) and
-                isset($sent_to_array['groups'][0]) and
+            if (isset($sent_to_array['groups']) &&
+                is_array($sent_to_array['groups']) &&
+                isset($sent_to_array['groups'][0]) &&
                 $sent_to_array['groups'][0] !== 0
             ) {
                 $group_id = $sent_to_array['groups'][0];
-                $output[] = "&nbsp;".$group_names[$group_id]['name'];
+
+                $users = GroupManager::getStudents($group_id, true);
+                $userToArray = [];
+                foreach ($users as $student) {
+                    $userToArray[] = $student['complete_name_with_username'];
             }
-            if (empty($sent_to_array['groups']) and empty($sent_to_array['users'])) {
+                $output[] =
+                    '<br />'.
+                    Display::label($group_names[$group_id]['name'], 'info').
+                    '&nbsp;'.implode(', ', $userToArray);
+            }
+            if (empty($sent_to_array['groups']) && empty($sent_to_array['users'])) {
                 $output[] = "&nbsp;".get_lang('Everybody');
             }
         }
@@ -1212,7 +1267,7 @@ class AnnouncementManager
         if (!empty($output)) {
             $output = array_filter($output);
             if (count($output) > 0) {
-                $output = implode(', ', $output);
+                $output = implode('<br />', $output);
             }
 
             return $output;
@@ -1459,6 +1514,8 @@ class AnnouncementManager
      * @param bool  $sendToDrhUsers
      * @param Monolog\Handler\HandlerInterface logger
      * @param int $senderId
+     *
+     * @return array
      */
     public static function sendEmail(
         $courseInfo,
@@ -1470,7 +1527,8 @@ class AnnouncementManager
         $senderId = 0
     ) {
         $email = new AnnouncementEmail($courseInfo, $sessionId, $announcementId, $logger);
-        $email->send($sendToUsersInSession, $sendToDrhUsers, $senderId);
+
+        return $email->send($sendToUsersInSession, $sendToDrhUsers, $senderId);
     }
 
     /**
@@ -1554,7 +1612,7 @@ class AnnouncementManager
         }
 
         if (!empty($userIdToSearch)) {
-            $userIdToSearch = intval($userIdToSearch);
+            $userIdToSearch = (int) $userIdToSearch;
             $searchCondition .= " AND (ip.insert_user_id = $userIdToSearch)";
         }
 
@@ -1564,10 +1622,17 @@ class AnnouncementManager
             $extraGroupCondition = " AND ip.to_group_id = $group_id ";
         }
 
+        $allowDrhAccess = api_get_configuration_value('allow_drh_access_announcement');
+
+        if ($allowDrhAccess && api_is_drh()) {
+            // DRH only can see visible
+            $searchCondition .= ' AND (ip.visibility = 1)';
+        }
+
         if (api_is_allowed_to_edit(false, true) ||
-            ($allowUserEditSetting && !api_is_anonymous())
+            ($allowUserEditSetting && !api_is_anonymous()) ||
+            ($allowDrhAccess && api_is_drh())
         ) {
-            // A.1. you are a course admin with a USER filter
             // => see only the messages of this specific user + the messages of the group (s)he is member of.
 
             //if (!empty($user_id)) {
@@ -1682,11 +1747,13 @@ class AnnouncementManager
                 } else {
                     if ($group_id == 0) {
                         $cond_user_id = " AND (
-                            (ip.to_user_id='$user_id' OR ip.to_user_id IS NULL) AND (ip.to_group_id IS NULL OR ip.to_group_id IN (0, ".implode(", ", $group_memberships)."))
+                            (ip.to_user_id='$user_id' OR ip.to_user_id IS NULL) AND 
+                            (ip.to_group_id IS NULL OR ip.to_group_id IN (0, ".implode(", ", $group_memberships)."))
                         ) ";
                     } else {
                         $cond_user_id = " AND (
-                            (ip.to_user_id='$user_id' OR ip.to_user_id IS NULL) AND (ip.to_group_id IS NULL OR ip.to_group_id IN (0, ".$group_id."))
+                            (ip.to_user_id='$user_id' OR ip.to_user_id IS NULL) AND 
+                            (ip.to_group_id IS NULL OR ip.to_group_id IN (0, ".$group_id."))
                         )";
                         $cond_user_id .= $extraGroupCondition;
                     }
@@ -1702,8 +1769,8 @@ class AnnouncementManager
                             ip.tool='announcement' 
                             $cond_user_id
                             $condition_session
-                            $searchCondition
-                            AND ip.visibility='1'
+                            $searchCondition AND 
+                            ip.visibility='1'
                         $groupBy
                         ORDER BY display_order DESC";
             } else {
@@ -1711,10 +1778,13 @@ class AnnouncementManager
                     if ($allowUserEditSetting && !api_is_anonymous()) {
                         $cond_user_id = " AND (
                             ip.lastedit_user_id = '".api_get_user_id()."' OR
-                            ((ip.to_user_id='$user_id' OR ip.to_user_id IS NULL) AND (ip.to_group_id='0' OR ip.to_group_id IS NULL))
+                                ((ip.to_user_id='$user_id' OR ip.to_user_id IS NULL) AND 
+                                (ip.to_group_id='0' OR ip.to_group_id IS NULL)
+                            )
                         ) ";
                     } else {
-                        $cond_user_id = " AND ((ip.to_user_id='$user_id' OR ip.to_user_id IS NULL) AND (ip.to_group_id='0' OR ip.to_group_id IS NULL) ) ";
+                        $cond_user_id = " AND ((ip.to_user_id='$user_id' OR ip.to_user_id IS NULL) AND 
+                        (ip.to_group_id='0' OR ip.to_group_id IS NULL) ) ";
                     }
 
                     $sql = "SELECT $select
@@ -1761,8 +1831,8 @@ class AnnouncementManager
         }
 
         if (!is_null($start) && !is_null($limit)) {
-            $start = intval($start);
-            $limit = intval($limit);
+            $start = (int) $start;
+            $limit = (int) $limit;
             $sql .= " LIMIT $start, $limit";
         }
 
@@ -1788,6 +1858,12 @@ class AnnouncementManager
             ICON_SIZE_SMALL
         );
 
+        $editIconDisable = Display::return_icon(
+            'edit_na.png',
+            get_lang('Edit'),
+            '',
+            ICON_SIZE_SMALL
+        );
         $deleteIcon = Display::return_icon(
             'delete.png',
             get_lang('Delete'),
@@ -1795,6 +1871,13 @@ class AnnouncementManager
             ICON_SIZE_SMALL
         );
 
+        $deleteIconDisable = Display::return_icon(
+            'delete_na.png',
+            get_lang('Delete'),
+            '',
+            ICON_SIZE_SMALL
+        );
+
         $isTutor = false;
         if (!empty($group_id)) {
             $groupInfo = GroupManager::get_group_properties(api_get_group_id());
@@ -1812,7 +1895,32 @@ class AnnouncementManager
                 if ($row['email_sent'] == '1') {
                     $sent_to_icon = ' '.$emailIcon;
                 }
-                $groupReference = ($row['to_group_id'] > 0) ? ' <span class="label label-info">'.get_lang('Group').'</span> ' : '';
+
+                $groupReference = $row['to_group_id'] > 0 ? ' <span class="label label-info">'.get_lang('Group').'</span> ' : '';
+                $disableEdit = false;
+                $to = self::loadEditUsers('announcement', $row['id'], true);
+                $separated = CourseManager::separateUsersGroups($to);
+                if (!empty($group_id)) {
+                    // If the announcement was sent to many groups, disable edition inside a group
+                    if (isset($separated['groups']) && count($separated['groups']) > 1) {
+                        $disableEdit = true;
+                    }
+
+                    // If the announcement was sent only to the course disable edition
+                    if (empty($separated['groups']) && empty($separated['users'])) {
+                        $disableEdit = true;
+                    }
+
+                    // Announcement sent to only a user
+                    if ($separated['groups'] > 1 && !in_array($group_id, $separated['groups'])) {
+                        $disableEdit = true;
+                    }
+                } else {
+                    if (isset($separated['groups']) && count($separated['groups']) > 1) {
+                        $groupReference = '';
+                    }
+                }
+
                 $title = $row['title'].$groupReference.$sent_to_icon;
                 $item_visibility = api_get_item_visibility(
                     $courseInfo,
@@ -1824,7 +1932,6 @@ class AnnouncementManager
 
                 // show attachment list
                 $attachment_list = self::get_attachment($row['id']);
-
                 $attachment_icon = '';
                 if (count($attachment_list) > 0) {
                     $attachment_icon = ' '.$attachmentIcon;
@@ -1832,7 +1939,7 @@ class AnnouncementManager
 
                 /* TITLE */
                 $user_info = api_get_user_info($row['insert_user_id']);
-                $username = sprintf(get_lang("LoginX"), $user_info['username']);
+                $username = sprintf(get_lang('LoginX'), $user_info['username']);
 
                 $username_span = Display::tag(
                     'span',
@@ -1852,7 +1959,12 @@ class AnnouncementManager
                     (api_get_course_setting('allow_user_edit_announcement') && !api_is_anonymous()) ||
                     ($row['to_group_id'] == $group_id && $isTutor)
                 ) {
+                    if ($disableEdit === true) {
+                        $modify_icons = "<a href='#'>".$editIconDisable."</a>";
+                    } else {
                     $modify_icons = "<a href=\"".$actionUrl."&action=modify&id=".$row['id']."\">".$editIcon."</a>";
+                    }
+
                     if ($row['visibility'] == 1) {
                         $image_visibility = "visible";
                         $alt_visibility = get_lang('Hide');
@@ -1860,6 +1972,7 @@ class AnnouncementManager
                         $image_visibility = "invisible";
                         $alt_visibility = get_lang('Visible');
                     }
+
                     $modify_icons .= "<a href=\"".$actionUrl."&action=showhide&id=".$row['id']."&sec_token=".$stok."\">".
                         Display::return_icon($image_visibility.'.png', $alt_visibility, '', ICON_SIZE_SMALL)."</a>";
 
@@ -1877,8 +1990,18 @@ class AnnouncementManager
                         $modify_icons .= Display::return_icon('down_na.gif', get_lang('Down'));
                     }
                     if (api_is_allowed_to_edit(false, true)) {
-                        $modify_icons .= "<a href=\"".$actionUrl."&action=delete&id=".$row['id']."&sec_token=".$stok."\" onclick=\"javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('ConfirmYourChoice'), ENT_QUOTES, api_get_system_encoding()))."')) return false;\">".
+                        if ($disableEdit === true) {
+                            $modify_icons .= Display::url($deleteIconDisable, '#');
+                        } else {
+                            $modify_icons .= "<a href=\"".$actionUrl."&action=delete&id=".$row['id']."&sec_token=".$stok."\" onclick=\"javascript:if(!confirm('".addslashes(
+                                    api_htmlentities(
+                                        get_lang('ConfirmYourChoice'),
+                                        ENT_QUOTES,
+                                        api_get_system_encoding()
+                                    )
+                                )."')) return false;\">".
                             $deleteIcon."</a>";
+                        }
                     }
                     $iterator++;
                 } else {

+ 24 - 35
main/inc/lib/document.lib.php

@@ -538,18 +538,17 @@ class DocumentManager
         $tblItemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
         $tblDocument = Database::get_course_table(TABLE_DOCUMENT);
 
-        $userGroupFilter = '';
         if (!is_null($toUserId)) {
-            $toUserId = intval($toUserId);
+            $toUserId = (int) $toUserId;
             $userGroupFilter = "last.to_user_id = $toUserId";
             if (empty($toUserId)) {
-                $userGroupFilter = " (last.to_user_id = 0 OR last.to_user_id IS NULL) ";
+                $userGroupFilter = ' (last.to_user_id = 0 OR last.to_user_id IS NULL) ';
             }
         } else {
-            $toGroupId = intval($toGroupId);
+            $toGroupId = (int) $toGroupId;
             $userGroupFilter = "last.to_group_id = $toGroupId";
             if (empty($toGroupId)) {
-                $userGroupFilter = "( last.to_group_id = 0 OR last.to_group_id IS NULL) ";
+                $userGroupFilter = '( last.to_group_id = 0 OR last.to_group_id IS NULL) ';
             }
         }
 
@@ -3366,7 +3365,6 @@ class DocumentManager
                     last.visibility <> 2 AND
                     docs.c_id = {$course_info['real_id']} AND
                     last.c_id = {$course_info['real_id']}
-                    $showOnlyFoldersCondition
                     $folderCondition
                     $levelCondition
                     $add_folder_filter
@@ -3427,7 +3425,7 @@ class DocumentManager
             }
         }
 
-        $write_result = self::write_resources_tree(
+        $writeResult = self::write_resources_tree(
             $userInfo,
             $course_info,
             $session_id,
@@ -3439,43 +3437,35 @@ class DocumentManager
             $folderId
         );
 
-        $return .= $write_result;
+        $return .= $writeResult;
+        $lpAjaxUrl = api_get_path(WEB_AJAX_PATH).'lp.ajax.php';
         if ($lp_id === false) {
-            $url = api_get_path(WEB_AJAX_PATH).
-                'lp.ajax.php?a=get_documents&url='.$overwrite_url.'&lp_id='.$lp_id.'&cidReq='.$course_info['code'];
+            $url = $lpAjaxUrl.'?a=get_documents&lp_id=&cidReq='.$course_info['code'];
             $return .= "<script>
-            $('.doc_folder').click(function() {
-                var realId = this.id;
-                var my_id = this.id.split('_')[2];
-                var tempId = 'temp_'+my_id;
-                $('#res_'+my_id).show();
-                var tempDiv = $('#'+realId).find('#'+tempId);
-                if (tempDiv.length == 0) {
-                    $.ajax({
-                        async: false,
-                        type: 'GET',
-                        url:  '".$url."',
-                        data: 'folder_id='+my_id,
-                        success: function(data) {
-                            $('#'+realId).append('<div id='+tempId+'>'+data+'</div>');
-                        }
-                    });
-                }
-            });
-
+            $(document).ready(function () {
             $('.close_div').click(function() {
                 var course_id = this.id.split('_')[2];
                 var session_id = this.id.split('_')[3];
                 $('#document_result_'+course_id+'_'+session_id).hide();
                 $('.lp_resource').remove();
                 $('.document_preview_container').html('');
+                });
             });
             </script>";
         } else {
             //For LPs
-            $url = api_get_path(WEB_AJAX_PATH).'lp.ajax.php?a=get_documents&lp_id='.$lp_id.'&'.api_get_cidreq();
-            $return .= "<script>
+            $url = $lpAjaxUrl.'?a=get_documents&lp_id='.$lp_id.'&'.api_get_cidreq();
+        }
 
+        if (!empty($overwrite_url)) {
+            $url .= '&url='.Security::remove_XSS($overwrite_url);
+        }
+
+        if ($add_move_button) {
+            $url .= '&add_move_button=1';
+        }
+
+            $return .= "<script>
             function testResources(id, img) {
                 var numericId = id.split('_')[1];
                 var parentId = 'doc_id_'+numericId;
@@ -3507,7 +3497,6 @@ class DocumentManager
                 }
             }
             </script>";
-        }
 
         if (!$user_in_course) {
             $return = '';
@@ -6649,15 +6638,15 @@ class DocumentManager
             return null;
         }
 
-        $onclick = '';
+        //$onclick = '';
         // if in LP, hidden folder are displayed in grey
         $folder_class_hidden = '';
         if ($lp_id) {
             if (isset($resource['visible']) && $resource['visible'] == 0) {
-                $folder_class_hidden = "doc_folder_hidden"; // in base.css
+                $folder_class_hidden = ' doc_folder_hidden'; // in base.css
             }
-            $onclick = 'onclick="javascript: testResources(\'res_'.$resource['id'].'\',\'img_'.$resource['id'].'\')"';
         }
+        $onclick = 'onclick="javascript: testResources(\'res_'.$resource['id'].'\',\'img_'.$resource['id'].'\')"';
         $return = null;
 
         if (empty($path)) {