瀏覽代碼

Minor - partial merge from 1.11.x

Julio Montoya 6 年之前
父節點
當前提交
1c13c69f26
共有 44 個文件被更改,包括 1190 次插入1009 次删除
  1. 132 17
      main/install/configuration.dist.php
  2. 276 43
      main/install/install.lib.php
  3. 1 8
      main/link/link.php
  4. 1 1
      main/lp/aicc_api.php
  5. 195 240
      main/lp/learnpath.class.php
  6. 12 10
      main/lp/learnpathItem.class.php
  7. 15 12
      main/lp/learnpathList.class.php
  8. 1 24
      main/lp/lp_add_item.php
  9. 2 0
      main/lp/lp_admin_view.php
  10. 1 1
      main/lp/lp_build.php
  11. 11 3
      main/lp/lp_controller.php
  12. 4 2
      main/lp/lp_edit.php
  13. 4 32
      main/lp/lp_edit_item.php
  14. 6 5
      main/lp/lp_final_item.php
  15. 2 2
      main/lp/lp_list.php
  16. 3 31
      main/lp/lp_move_item.php
  17. 4 4
      main/lp/lp_stats.php
  18. 2 0
      main/lp/lp_subscribe_users_to_category.php
  19. 6 6
      main/lp/lp_view.php
  20. 2 2
      main/lp/lp_view_item.php
  21. 7 4
      main/lp/openoffice_document.class.php
  22. 279 214
      main/lp/packaging/assets/api_wrapper.js
  23. 16 33
      main/lp/scorm.class.php
  24. 32 38
      main/lp/scorm_api.php
  25. 6 1
      main/portfolio/add_category.php
  26. 6 1
      main/portfolio/add_item.php
  27. 6 1
      main/portfolio/edit_category.php
  28. 7 2
      main/portfolio/edit_item.php
  29. 2 2
      main/portfolio/index.php
  30. 53 44
      main/session/about.php
  31. 3 3
      main/social/personal_data.php
  32. 27 6
      main/survey/create_new_survey.php
  33. 13 4
      main/survey/survey.lib.php
  34. 4 12
      main/tracking/courseLog.php
  35. 5 7
      main/tracking/course_log_events.php
  36. 6 8
      main/tracking/course_log_groups.php
  37. 7 10
      main/tracking/course_log_resources.php
  38. 7 12
      main/tracking/course_log_tools.php
  39. 3 4
      main/tracking/course_session_report.php
  40. 3 6
      main/tracking/lp_results_by_user.php
  41. 1 1
      main/tracking/question_course_report.php
  42. 13 14
      main/tracking/total_time.php
  43. 2 109
      main/work/work.lib.php
  44. 2 30
      main/work/work.php

+ 132 - 17
main/install/configuration.dist.php

@@ -112,6 +112,16 @@ $_configuration['hosting_total_size_limit'] = 0;
  * don't need it. These settings are for simple Origin Pull CDNs and are
  * experimental. Enable only if you really know what you're doing.
  * This might conflict with multiple-access urls.
+ * Please note that recent browsers forbid the loading of resources from
+ * a different portal URL then where they are, due to CORS rules.
+ * To allow for CDN usage with different URLs, you need to specifically
+ * allow CORS Access-Control-Allow-Origin for your main Chamilo URL.
+ * This has to be done at the web server level, because Chamilo's PHP code
+ * doesn't change HTTP headers of all files served from the Chamilo directory.
+ * To do that on Apache, use
+ *   Header set Access-Control-Allow-Origin "http(s)://main-chamilo-url"
+ * in Nginx:
+ *   add_header 'Access-Control-Allow-Origin' 'http(s)://main-chamilo-url';.
  */
 // Set the following setting to true to start using the CDN
 $_configuration['cdn_enable'] = false;
@@ -374,7 +384,20 @@ $_configuration['agenda_colors'] = [
 */
 // ------
 //
-// Save some tool titles with HTML editor
+// Save some tool titles with HTML editor. Require DB changes:
+/*
+ALTER TABLE course_category CHANGE name name LONGTEXT NOT NULL;
+ALTER TABLE c_course_description CHANGE title title LONGTEXT NOT NULL;
+ALTER TABLE c_thematic CHANGE title title LONGTEXT NOT NULL;
+ALTER TABLE c_quiz CHANGE title title LONGTEXT NOT NULL;
+ALTER TABLE c_lp_category CHANGE name name LONGTEXT NOT NULL;
+ALTER TABLE c_glossary CHANGE name name LONGTEXT NOT NULL;
+ALTER TABLE c_tool CHANGE name name LONGTEXT NOT NULL;
+-- Only with allow_portfolio_tool enabled
+ALTER TABLE portfolio CHANGE title title LONGTEXT NOT NULL;
+ALTER TABLE portfolio_category CHANGE title title LONGTEXT NOT NULL;
+--
+*/
 // $_configuration['save_titles_as_html'] = false;
 // Show the full toolbar set to all CKEditor
 //$_configuration['full_ckeditor_toolbar_set'] = false;
@@ -440,18 +463,28 @@ $_configuration['agenda_colors'] = [
 // X-Frame-Options tells the browser whether you want to allow your site to
 // be framed or not. By preventing a browser from framing your site you can
 // defend against attacks like clickjacking.
-// Recommended value "x-frame-options: SAMEORIGIN".
-//$_configuration['security_x_frame_options'] = 'x-frame-options: SAMEORIGIN';
+// If defining a URL here, it should define the URL(s) from which your content
+// should be visible, not the URLs from which your site accepts content.
+// For example, if your main URL (root_web above) is https://11.chamilo.org/,
+// then this setting should be: 'ALLOW-FROM https://11.chamilo.org'.
+// These headers only apply to pages where Chamilo is responsible of the HTTP
+// headers generation (i.e. ".php" files). It does not apply to static files.
+// If playing with this feature, make sure you also update your web server
+// configuration to add the right headers for static files. See CDN
+// configuration documentation above (search for "add_header") for more
+// information.
+// Recommended (strict) value for this setting, if enabled: "SAMEORIGIN".
+//$_configuration['security_x_frame_options'] = 'SAMEORIGIN';
 //
 // X-XSS-Protection sets the configuration for the cross-site scripting
 // filter built into most browsers.
-// Recommended value "X-XSS-Protection: 1; mode=block".
-//$_configuration['security_xss_protection'] = 'X-XSS-Protection: 1; mode=block';
+// Recommended value "1; mode=block".
+//$_configuration['security_xss_protection'] = '1; mode=block';
 //
 // X-Content-Type-Options stops a browser from trying to MIME-sniff the
 // content type and forces it to stick with the declared content-type. The only
-// valid value for this header is "X-Content-Type-Options: nosniff".
-//$_configuration['security_x_content_type_options'] = 'X-Content-Type-Options: nosniff';
+// valid value for this header is "nosniff".
+//$_configuration['security_x_content_type_options'] = 'nosniff';
 //
 // Referrer Policy is a new header that allows a site to control how much
 // information the browser includes with navigation away from a document
@@ -481,6 +514,10 @@ ALTER TABLE c_survey_question ADD is_required TINYINT(1) DEFAULT 0 NOT NULL;
 // Hide survey edition tools for all or some surveys.
 //Set an asterisk to hide for all, otherwise set an array with the survey codes in which the options will be blocked
 //$_configuration['hide_survey_edition'] = ['codes' => []];
+// Allows to set the date and time of availability for surveys. Requires DB changes:
+// ALTER TABLE c_survey CHANGE avail_from avail_from DATETIME DEFAULT NULL, CHANGE avail_till avail_till DATETIME DEFAULT NULL;
+// Requires change the Doctrine type from date to datime in CSurvey::$availFrom and CSurvey::$availTill
+//$_configuration['allow_survey_availability_datetime'] = false;
 // ------
 
 // Allow career diagram, requires a DB change:
@@ -522,6 +559,8 @@ $_configuration['send_all_emails_to'] = [
 //$_configuration['hide_search_form_in_session_list'] = false;
 // Allow exchange of messages from teachers/bosses about a user.
 //$_configuration['private_messages_about_user'] = false;
+// Allow the messages to be visible for the students
+//$_configuration['private_messages_about_user_visible_to_user'] = false;
 // Allow send email notification per exercise
 //ALTER TABLE c_quiz ADD COLUMN notifications VARCHAR(255) NULL DEFAULT NULL;
 //$_configuration['allow_notification_setting_per_exercise'] = false;
@@ -603,7 +642,7 @@ $_configuration['score_grade_model'] = [
 //$_configuration['my_courses_list_as_category'] = false;
 // ------
 
-// Skills can only visible for admins, teachers (related to a user via a course),
+// Skills can only be visible for admins, teachers (related to a user via a course),
 // and HRM users (if related to a user).
 // $_configuration['allow_private_skills'] = false;
 // Additional gradebook dependencies BT#13099
@@ -731,6 +770,7 @@ $_configuration['gradebook_badge_sidebar'] = [
 // Order sessions
 // Requires DB change: ALTER TABLE session ADD COLUMN position INT DEFAULT 0;
 // Requires edit Entity Session: src/Chamilo/CoreBundle/Entity/Session.php uncomment "position" variable.
+// Requires uncomment the position get and set
 //$_configuration['session_list_order'] = false;
 
 // Show skills as a hierarchical table
@@ -800,17 +840,17 @@ ALTER TABLE skill_rel_course ADD CONSTRAINT FK_E7CEC7FA613FECDF FOREIGN KEY (ses
 /*
 CREATE TABLE portfolio (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, c_id INT DEFAULT NULL, session_id INT DEFAULT NULL, category_id INT DEFAULT NULL, title VARCHAR(255) NOT NULL, content LONGTEXT NOT NULL, creation_date DATETIME NOT NULL, update_date DATETIME NOT NULL, is_visible TINYINT(1) DEFAULT '1' NOT NULL, INDEX user (user_id), INDEX course (c_id), INDEX session (session_id), INDEX category (category_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;
 CREATE TABLE portfolio_category (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, title VARCHAR(255) NOT NULL, description LONGTEXT DEFAULT NULL, is_visible TINYINT(1) DEFAULT '1' NOT NULL, INDEX user (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;
-ALTER TABLE portfolio ADD CONSTRAINT FK_A9ED1062A76ED395 FOREIGN KEY (user_id) REFERENCES user (id);
-ALTER TABLE portfolio ADD CONSTRAINT FK_A9ED106291D79BD3 FOREIGN KEY (c_id) REFERENCES course (id);
-ALTER TABLE portfolio ADD CONSTRAINT FK_A9ED1062613FECDF FOREIGN KEY (session_id) REFERENCES session (id);
-ALTER TABLE portfolio ADD CONSTRAINT FK_A9ED106212469DE2 FOREIGN KEY (category_id) REFERENCES portfolio_category (id);
+ALTER TABLE portfolio ADD CONSTRAINT FK_A9ED1062A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE;
+ALTER TABLE portfolio ADD CONSTRAINT FK_A9ED106291D79BD3 FOREIGN KEY (c_id) REFERENCES course (id) ON DELETE CASCADE;
+ALTER TABLE portfolio ADD CONSTRAINT FK_A9ED1062613FECDF FOREIGN KEY (session_id) REFERENCES session (id) ON DELETE CASCADE;
+ALTER TABLE portfolio ADD CONSTRAINT FK_A9ED106212469DE2 FOREIGN KEY (category_id) REFERENCES portfolio_category (id) ON DELETE SET NULL;
 ALTER TABLE portfolio_category ADD CONSTRAINT FK_7AC64359A76ED395 FOREIGN KEY (user_id) REFERENCES user (id);
 INSERT INTO settings_current(variable, subkey, type, category, selected_value, title, comment, scope, subkeytext, access_url_changeable) VALUES('course_create_active_tools','portfolio','checkbox','Tools','true','CourseCreateActiveToolsTitle','CourseCreateActiveToolsComment',NULL,'Portfolio', 0);
 */
 //$_configuration['allow_portfolio_tool'] = false;
 
-// Disable average and best columns in gradebook see BT#14126
-//$_configuration['disable_gradebook_stats'] = false;
+// Enable best score column in gradebook. Previously called disable_gradebook_stats
+//$_configuration['gradebook_enable_best_score'] = false;
 
 // Allow teachers to access student skills BT#14161 (skills setting must be enabled in the platform)
 //$_configuration['allow_teacher_access_student_skills'] = false;
@@ -833,15 +873,90 @@ INSERT INTO settings_current(variable, subkey, type, category, selected_value, t
 // Enable speed controller in video player
 // $_configuration['video_features'] = ['features' => ['speed']];
 
+// Disable token verification when sending a message
+// $_configuration['disable_token_in_new_message'] = false;
+
 // My courses session order. Possible field values: "start_date" or "end_date". Order values: "asc" or "desc"
 // $_configuration['my_courses_session_order'] = ['field' => 'end_date', 'order' => 'desc'];
 
+// Allow set courses in session in read-only mode. Require DB changes:
+/*
+INSERT INTO extra_field (extra_field_type, field_type, variable, display_text, visible_to_self, changeable, filter, created_at)
+VALUES (2, 13, 'session_courses_read_only_mode', 'Lock Course In Session', 1, 1, 1, NOW());
+*/
+// $_configuration['session_courses_read_only_mode'] = false;
+
+// Allow SCORM packages when importing a course
+// $_configuration['allow_import_scorm_package_in_course_builder'] = false;
+
+// Hide announcement "sent to" label
+// $_configuration['hide_announcement_sent_to_users_info'] = false;
+
+// Hide gradebook graph
+// $_configuration['gradebook_hide_graph'] = false;
+
+// Hide gradebook "download report in PDF" button
+// $_configuration['gradebook_hide_pdf_report_button'] = false;
+
+// Show pending survey link in user menu
+// $_configuration['show_pending_survey_in_menu'] = false;
+
+// Show multiple conditions to user during sign up process
+// Example with a GDPR condition
+/*$_configuration['show_conditions_to_user'] = [
+    'conditions' => [
+        [
+            'variable' => 'gdpr', // internal extra field name
+            'display_text' => 'GDPRTitle', // checkbox title will be translated with get_lang('GDPRTitle')
+            'text_area' => 'GDPRTextArea', // this will be translated using get_lang('GDPRTextArea')
+        ],
+        [
+            'variable' => 'my_terms',
+            'display_text' => 'My test conditions',
+            'text_area' => 'This is a long text area, with lot of terms and conditions ... ',
+        ],
+    ],
+];*/
+
+// Hide LP item prerequisite label in the LP view
+//$_configuration['hide_accessibility_label_on_lp_item'] = true;
+
+// Round score in exercise category export
+//$_configuration['exercise_category_round_score_in_export'] = false;
+
+// Redirect index to url for logged in users
+// In this example the index.php will be redirected to user_portal.php for logged in users
+//$_configuration['redirect_index_to_url_for_logged_users'] = 'user_portal.php';
+
+// Teachers can CRUD classes
+// ALTER TABLE usergroup ADD author_id INT DEFAULT NULL;
+//$_configuration['allow_teachers_to_classes'] = false;
+
+// GDPR: European's General Data Protection Rules activation option
+// Set to true to automatically enable a new personal data page inside the social network menu
+// $_configuration['enable_gdpr'] = false;
+
+// GDPR requires users to be informed of the Data Protection Officer name and contact point
+// These can only be defined here for now, but will be moved to web settings in the future.
+// Name of the person or organization that is responsible for the treatment of personal info
+//$_configuration['data_protection_officer_name'] = '';
+// A description of the role of the DP Officer in this context
+//$_configuration['data_protection_officer_role'] = '';
+// An e-mail address where to contact the data protection officer for queries
+//$_configuration['data_protection_officer_email'] = '';
+
+// Validate user login via a webservice, Chamilo will send a "login" and "password" parameters
+// to the "myWebServiceFunctionToLogin" function, the result should be "1" if the user have access.
+/*$_configuration['webservice_validation'] = [
+    'options' => [
+        'wsdl' => 'https://example.com/soap?wsdl',
+        'check_login_function' => 'myWebServiceFunctionToLogin'
+    ]
+];*/
+
 // ------ Custom DB changes (keep this at the end)
 // Add user activation by confirmation email
 // This option prevents the new user to login in the platform if your account is not confirmed via email
 // You need add a new option called "confirmation" to the registration settings
 //INSERT INTO settings_options (variable, value, display_text) VALUES ('allow_registration', 'confirmation', 'MailConfirmation');
 // ------ (End) Custom DB changes
-// You need add a new option called "confirmation" to the registration settings
-//INSERT INTO settings_options (variable, value, display_text) VALUES ('allow_registration', 'confirmation', 'MailConfirmation');
-// ------ (End) Custom DB changes

+ 276 - 43
main/install/install.lib.php

@@ -19,7 +19,7 @@ use Sonata\PageBundle\Entity\PageManager;
  *   of older versions before upgrading.
  */
 
-/*      CONSTANTS */
+/* CONSTANTS */
 define('SYSTEM_CONFIG_FILENAME', 'configuration.dist.php');
 
 /**
@@ -172,7 +172,15 @@ function checkAccessUrl()
  */
 function getPhpSetting($val)
 {
-    return ini_get($val) == '1' ? 'ON' : 'OFF';
+    $value = ini_get($val);
+    switch ($val) {
+        case 'display_errors':
+            global $originalDisplayErrors;
+            $value = $originalDisplayErrors;
+            break;
+    }
+
+    return $value == '1' ? 'ON' : 'OFF';
 }
 
 /**
@@ -412,19 +420,21 @@ function write_system_config_file($path)
     $fp = @fopen($path, 'w');
 
     if (!$fp) {
-        echo '<strong><font color="red">Your script doesn\'t have write access to the config directory</font></strong><br />
-                        <em>('.str_replace('\\', '/', realpath($path)).')</em><br /><br />
-                        You probably do not have write access on Chamilo root directory,
-                        i.e. you should <em>CHMOD 777</em> or <em>755</em> or <em>775</em>.<br /><br />
-                        Your problems can be related on two possible causes:<br />
-                        <ul>
-                          <li>Permission problems.<br />Try initially with <em>chmod -R 777</em> and increase restrictions gradually.</li>
-                          <li>PHP is running in <a href="http://www.php.net/manual/en/features.safe-mode.php" target="_blank">Safe-Mode</a>. If possible, try to switch it off.</li>
-                        </ul>
-                        <a href="http://forum.chamilo.org/" target="_blank">Read about this problem in Support Forum</a><br /><br />
-                        Please go back to step 5.
-                        <p><input type="submit" name="step5" value="&lt; Back" /></p>
-                        </td></tr></table></form></body></html>';
+        echo '<strong>
+                <font color="red">Your script doesn\'t have write access to the config directory</font></strong><br />
+                <em>('.str_replace('\\', '/', realpath($path)).')</em><br /><br />
+                You probably do not have write access on Chamilo root directory,
+                i.e. you should <em>CHMOD 777</em> or <em>755</em> or <em>775</em>.<br /><br />
+                Your problems can be related on two possible causes:<br />
+                <ul>
+                  <li>Permission problems.<br />Try initially with <em>chmod -R 777</em> and increase restrictions gradually.</li>
+                  <li>PHP is running in <a href="http://www.php.net/manual/en/features.safe-mode.php" target="_blank">Safe-Mode</a>. 
+                  If possible, try to switch it off.</li>
+                </ul>
+                <a href="http://forum.chamilo.org/" target="_blank">Read about this problem in Support Forum</a><br /><br />
+                Please go back to step 5.
+                <p><input type="submit" name="step5" value="&lt; Back" /></p>
+                </td></tr></table></form></body></html>';
         exit;
     }
 
@@ -738,7 +748,7 @@ function display_requirements(
     $updatePath = '',
     $update_from_version_8 = []
 ) {
-    global $_setting;
+    global $_setting, $originalMemoryLimit;
     echo '<div class="RequirementHeading"><h2>'.display_step_sequence().get_lang('Requirements')."</h2></div>";
     echo '<div class="RequirementText">';
     echo '<strong>'.get_lang('ReadThoroughly').'</strong><br />';
@@ -920,7 +930,7 @@ function display_requirements(
             <tr>
                 <td class="requirements-item"><a href="http://www.php.net/manual/en/ini.core.php#ini.memory-limit">Memory Limit</a></td>
                 <td class="requirements-recommended">'.Display::label('>= '.REQUIRED_MIN_MEMORY_LIMIT.'M', 'success').'</td>
-                <td class="requirements-value">'.compare_setting_values(ini_get('memory_limit'), REQUIRED_MIN_MEMORY_LIMIT).'</td>
+                <td class="requirements-value">'.compare_setting_values($originalMemoryLimit, REQUIRED_MIN_MEMORY_LIMIT).'</td>
             </tr>
           </table>';
     echo '  </div>';
@@ -933,9 +943,9 @@ function display_requirements(
 
     $course_attempt_name = '__XxTestxX__';
     $course_dir = api_get_path(SYS_COURSE_PATH).$course_attempt_name;
-
+    $fileToCreate = 'test.html';
     //Just in case
-    @unlink($course_dir.'/test.php');
+    @unlink($course_dir.'/'.$fileToCreate);
     @rmdir($course_dir);
 
     $perms_dir = [0777, 0755, 0775, 0770, 0750, 0700];
@@ -958,17 +968,17 @@ function display_requirements(
             if ($file_course_test_was_created == true) {
                 break;
             }
-            $r = @touch($course_dir.'/test.php', $perm);
+            $r = @touch($course_dir.'/'.$fileToCreate, $perm);
             if ($r === true) {
                 $fil_perm_verified = $perm;
-                if (check_course_script_interpretation($course_dir, $course_attempt_name, 'test.php')) {
+                if (checkCourseScriptCreation($course_dir, $course_attempt_name, $fileToCreate)) {
                     $file_course_test_was_created = true;
                 }
             }
         }
     }
 
-    @unlink($course_dir.'/test.php');
+    @unlink($course_dir.'/'.$fileToCreate);
     @rmdir($course_dir);
 
     $_SESSION['permissions_for_new_directories'] = $_setting['permissions_for_new_directories'] = $dir_perm_verified;
@@ -986,7 +996,7 @@ function display_requirements(
         $courseTestLabel = Display::label(get_lang('Warning'), 'warning');
         $courseTestLabel .= '<br />'.sprintf(
             get_lang('InstallWarningCouldNotInterpretPHP'),
-            api_get_path(WEB_COURSE_PATH).$course_attempt_name.'/test.php'
+            api_get_path(WEB_COURSE_PATH).$course_attempt_name.'/'.$fileToCreate
         );
     }
 
@@ -1072,8 +1082,8 @@ function display_requirements(
     } else {
         $error = false;
         // First, attempt to set writing permissions if we don't have them yet
-        $perm = octdec('0770');
-        $perm_file = octdec('0770');
+        $perm = api_get_permissions_for_new_directories();
+        $perm_file = api_get_permissions_for_new_files();
         $notWritable = [];
 
         $checked_writable = api_get_path(SYS_APP_PATH);
@@ -1105,6 +1115,7 @@ function display_requirements(
         }
 
         // Second, if this fails, report an error
+
         //--> The user would have to adjust the permissions manually
         if (count($notWritable) > 0) {
             $error = true; ?>
@@ -1134,17 +1145,15 @@ function display_requirements(
             api_get_path(SYS_PLUGIN_PATH).'skype/',
         ];
         $deprecatedToRemove = [];
-
         foreach ($deprecated as $deprecatedDirectory) {
             if (!is_dir($deprecatedDirectory)) {
                 continue;
             }
-
             $deprecatedToRemove[] = $deprecatedDirectory;
         }
 
-        if (count($deprecatedToRemove) > 0) {
-            $error = true; ?>
+        if (count($deprecatedToRemove) > 0) { 
+?>
             <p class="text-danger"><?php echo get_lang('WarningForDeprecatedDirectoriesForUpgrade'); ?></p>
             <ul>
                 <?php foreach ($deprecatedToRemove as $deprecatedDirectory) {
@@ -1611,6 +1620,8 @@ function panel($content = null, $title = null, $id = null, $style = null)
  * @param string $formFieldName
  * @param string $parameterValue
  * @param string $displayWhenUpdate
+ *
+ * @return string
  */
 function display_configuration_parameter(
     $installType,
@@ -1678,7 +1689,13 @@ function display_configuration_settings_form(
 
     // Parameter 1: administrator's login
     $html = '';
-    $html .= display_configuration_parameter($installType, get_lang('AdminLogin'), 'loginForm', $loginForm, $installType == 'update');
+    $html .= display_configuration_parameter(
+        $installType,
+        get_lang('AdminLogin'),
+        'loginForm',
+        $loginForm,
+        $installType == 'update'
+    );
 
     // Parameter 2: administrator's password
     if ($installType != 'update') {
@@ -1687,7 +1704,12 @@ function display_configuration_settings_form(
 
     // Parameters 3 and 4: administrator's names
 
-    $html .= display_configuration_parameter($installType, get_lang('AdminFirstName'), 'adminFirstName', $adminFirstName);
+    $html .= display_configuration_parameter(
+        $installType,
+        get_lang('AdminFirstName'),
+        'adminFirstName',
+        $adminFirstName
+    );
     $html .= display_configuration_parameter($installType, get_lang('AdminLastName'), 'adminLastName', $adminLastName);
 
     //Parameter 3: administrator's email
@@ -1894,8 +1916,6 @@ function get_countries_list_from_array($combo = false)
         "Yemen",
         "Zambia", "Zimbabwe",
     ];
-
-    $country_select = '';
     if ($combo) {
         $country_select = '<select class="selectpicker show-tick" id="country" name="country">';
         $country_select .= '<option value="">--- '.get_lang('SelectOne').' ---</option>';
@@ -1968,24 +1988,24 @@ function compare_setting_values($current_value, $wanted_value)
 }
 
 /**
- * @param $course_dir
- * @param $course_attempt_name
+ * @param string $course_dir
+ * @param string $course_attempt_name
  * @param string $file
  *
  * @return bool
  */
-function check_course_script_interpretation(
+function checkCourseScriptCreation(
     $course_dir,
     $course_attempt_name,
-    $file = 'test.php'
+    $file
 ) {
     $output = false;
     //Write in file
     $file_name = $course_dir.'/'.$file;
-    $content = '<?php echo "123"; exit;';
+    $content = '123';
 
     if (is_writable($file_name)) {
-        if ($handler = @fopen($file_name, "w")) {
+        if ($handler = @fopen($file_name, 'w')) {
             //write content
             if (fwrite($handler, $content)) {
                 $sock_errno = '';
@@ -2126,6 +2146,7 @@ function migrate($chamiloVersion, EntityManager $manager)
 {
     $debug = true;
     $connection = $manager->getConnection();
+
     $config = new \Doctrine\DBAL\Migrations\Configuration\Configuration($connection);
 
     // Table name that will store migrations log (will be created automatically,
@@ -2145,7 +2166,9 @@ function migrate($chamiloVersion, EntityManager $manager)
     foreach ($versions as $version) {
         $version->getMigration()->setEntityManager($manager);
     }
+
     $to = null; // if $to == null then schema will be migrated to latest version
+
     echo "<pre>";
     try {
         // Execute migration!
@@ -2165,6 +2188,7 @@ function migrate($chamiloVersion, EntityManager $manager)
                     $counter++;
                 }
             }
+
             echo "<br>DONE!<br>";
         }
 
@@ -2176,6 +2200,7 @@ function migrate($chamiloVersion, EntityManager $manager)
             return false;
         }
     }
+
     echo "</pre>";
 
     return false;
@@ -2266,7 +2291,6 @@ function fixIds(EntityManager $em)
 
         if (!empty($sql) && !empty($newId) && !empty($iid)) {
             $sql = "UPDATE c_lp_item SET ref = $newId WHERE iid = $iid";
-
             $connection->executeQuery($sql);
         }
     }
@@ -2431,6 +2455,7 @@ function fixIds(EntityManager $em)
     $result = $connection->fetchAll($sql);
     foreach ($result as $item) {
         $courseCode = $item['course_code'];
+        $categoryId = (int) $item['category_id'];
 
         $sql = "SELECT * FROM course WHERE code = '$courseCode'";
         $courseInfo = $connection->fetchAssoc($sql);
@@ -2467,7 +2492,7 @@ function fixIds(EntityManager $em)
             if (isset($data) && isset($data['iid'])) {
                 $newId = $data['iid'];
                 $sql = "UPDATE gradebook_link SET ref_id = $newId
-                        WHERE id = $iid";
+                        WHERE id = $iid AND course_code = '$courseCode' AND category_id = $categoryId ";
                 $connection->executeQuery($sql);
             }
         }
@@ -2480,9 +2505,7 @@ function fixIds(EntityManager $em)
     $sql = "SELECT * FROM groups";
     $result = $connection->executeQuery($sql);
     $groups = $result->fetchAll();
-
     $oldGroups = [];
-
     if (!empty($groups)) {
         foreach ($groups as $group) {
             if (empty($group['name'])) {
@@ -2741,6 +2764,216 @@ function fixIds(EntityManager $em)
     if ($debug) {
         error_log('Finish fixId function');
     }
+
+    fixLpId($connection, true);
+}
+
+/**
+ * @param \Doctrine\DBAL\Connection $connection
+ * @param $debug
+ *
+ * @throws \Doctrine\DBAL\DBALException
+ */
+function fixLpId($connection, $debug)
+{
+    if ($debug) {
+        error_log('Fix lp.id lp.iids');
+    }
+
+    $sql = 'SELECT id, title, code FROM course';
+    $result = $connection->query($sql);
+    $courses = $result->fetchAll();
+
+    $sql = 'SELECT id FROM session';
+    $result = $connection->query($sql);
+    $sessions = $result->fetchAll();
+
+    $tblCLp = Database::get_course_table(TABLE_LP_MAIN);
+    $tblCLpItem = Database::get_course_table(TABLE_LP_ITEM);
+    $toolTable = Database::get_course_table(TABLE_TOOL_LIST);
+
+    if (!empty($sessions)) {
+        $sessions = array_column($sessions, 'id');
+        $sessions[] = 0;
+    } else {
+        $sessions = [0];
+    }
+
+    foreach ($courses as $course) {
+        $courseId = $course['id'];
+        $sql = "SELECT * FROM $tblCLp WHERE c_id = $courseId AND iid <> id ORDER by iid";
+        $result = $connection->query($sql);
+        if ($debug) {
+            error_log('-------------');
+            error_log("Entering Lps in course #$courseId");
+            error_log($sql);
+        }
+        $lpList = $result->fetchAll();
+        $myOnlyLpList = [];
+        if (!empty($lpList)) {
+            foreach ($lpList as $lpInfo) {
+                $oldId = $lpInfo['id'];
+                $sql = "SELECT * FROM $tblCLpItem WHERE c_id = $courseId AND lp_id = $oldId ORDER by iid";
+                $result = $connection->query($sql);
+                $items = $result->fetchAll();
+                $lpInfo['lp_list'] = $items;
+                $myOnlyLpList[] = $lpInfo;
+            }
+        }
+
+        if (!empty($myOnlyLpList)) {
+            foreach ($myOnlyLpList as $lpInfo) {
+                $lpIid = $lpInfo['iid'];
+                $oldId = $lpInfo['id'];
+                $items = $lpInfo['lp_list'];
+
+                if (empty($items)) {
+                    continue;
+                }
+                $itemList = [];
+                foreach ($items as $subItem) {
+                    $itemList[$subItem['id']] = $subItem['iid'];
+                }
+                $variablesToFix = [
+                    'parent_item_id',
+                    'next_item_id',
+                    'prerequisite',
+                    'previous_item_id',
+                ];
+
+                foreach ($sessions as $sessionId) {
+                    $correctLink = "lp/lp_controller.php?action=view&lp_id=$lpIid&id_session=$sessionId";
+                    $link = "newscorm/lp_controller.php?action=view&lp_id=$oldId&id_session=$sessionId";
+                    $secondLink = "lp/lp_controller.php?action=view&lp_id=$oldId&id_session=$sessionId";
+                    $sql = "UPDATE $toolTable 
+                        SET link = '$correctLink'
+                        WHERE c_id = $courseId AND (link = '$link' OR link ='$secondLink')";
+                    $connection->query($sql);
+                    if ($debug) {
+                        //error_log("Fix wrong c_tool links");
+                        //error_log($sql);
+                    }
+                }
+
+                foreach ($items as $item) {
+                    $itemIid = $item['iid'];
+                    $itemId = $item['id'];
+                    foreach ($variablesToFix as $variable) {
+                        if (!empty($item[$variable]) && isset($itemList[$item[$variable]])) {
+                            $newId = $itemList[$item[$variable]];
+                            $sql = "UPDATE $tblCLpItem SET $variable = $newId 
+                                    WHERE iid = $itemIid AND c_id = $courseId AND lp_id = $oldId";
+                            $connection->query($sql);
+                            if ($debug) {
+                                //error_log($sql);
+                            }
+                        }
+                    }
+
+                    if ($item['item_type'] == 'document' && !empty($item['path'])) {
+                        $oldDocumentId = $item['path'];
+                        $sql = "SELECT * FROM c_document WHERE c_id = $courseId AND id = $oldDocumentId";
+                        $result = $connection->query($sql);
+                        $document = $result->fetch();
+                        if (!empty($document)) {
+                            $newDocumentId = $document['iid'];
+                            if (!empty($newDocumentId)) {
+                                $sql = "UPDATE $tblCLpItem SET path = $newDocumentId 
+                                    WHERE iid = $itemIid AND c_id = $courseId";
+                                $connection->query($sql);
+                                if ($debug) {
+                                    //error_log("Fix document: ");
+                                    //error_log($sql);
+                                }
+                            }
+                        }
+                    }
+
+                    // c_lp_view
+                    $sql = "UPDATE c_lp_view SET last_item = $itemIid 
+                            WHERE c_id = $courseId AND last_item = $itemId AND lp_id = $oldId";
+                    $connection->query($sql);
+
+                    // c_lp_item_view
+                    $sql = "UPDATE c_lp_item_view SET lp_item_id = $itemIid 
+                            WHERE c_id = $courseId AND lp_item_id = $itemId";
+                    $connection->query($sql);
+
+                    // Update track_exercises
+                    $sql = "UPDATE track_e_exercises SET orig_lp_item_id = $itemIid 
+                            WHERE c_id = $courseId AND orig_lp_id = $oldId AND orig_lp_item_id = $itemId";
+                    $connection->query($sql);
+
+                    // c_forum_thread
+                    $sql = "UPDATE c_forum_thread SET lp_item_id = $itemIid 
+                            WHERE c_id = $courseId AND lp_item_id = $itemId";
+                    $connection->query($sql);
+
+                    // orig_lp_item_view_id
+                    $sql = "SELECT * FROM c_lp_view
+                            WHERE c_id = $courseId AND lp_id = $oldId";
+                    $result = $connection->query($sql);
+                    $itemViewList = $result->fetchAll();
+                    if ($itemViewList) {
+                        foreach ($itemViewList as $itemView) {
+                            $userId = $itemView['user_id'];
+                            $oldItemViewId = $itemView['id'];
+                            $newItemView = $itemView['iid'];
+
+                            if (empty($oldItemViewId)) {
+                                continue;
+                            }
+
+                            $sql = "UPDATE track_e_exercises 
+                                SET orig_lp_item_view_id = $newItemView 
+                                WHERE 
+                                  c_id = $courseId AND 
+                                  orig_lp_id = $oldId AND 
+                                  orig_lp_item_id = $itemIid AND 
+                                  orig_lp_item_view_id = $oldItemViewId AND 
+                                  exe_user_id = $userId                                       
+                                  ";
+                            $connection->query($sql);
+
+                            /*$sql = "UPDATE c_lp_item_view
+                                    SET lp_view_id = $newItemView
+                                    WHERE
+                                      lp_view_id = $oldItemViewId AND
+                                      c_id = $courseId
+                                  ";
+                            $connection->query($sql);*/
+                        }
+                    }
+
+                    $sql = "UPDATE $tblCLpItem SET lp_id = $lpIid 
+                            WHERE c_id = $courseId AND lp_id = $oldId AND id = $itemId";
+                    $connection->query($sql);
+
+                    $sql = "UPDATE $tblCLpItem SET id = iid 
+                            WHERE c_id = $courseId AND lp_id = $oldId AND id = $itemId";
+                    $connection->query($sql);
+                }
+
+                $sql = "UPDATE c_lp_view SET lp_id = $lpIid WHERE c_id = $courseId AND lp_id = $oldId";
+                $connection->query($sql);
+
+                $sql = "UPDATE c_forum_forum SET lp_id = $lpIid WHERE c_id = $courseId AND lp_id = $oldId";
+                $connection->query($sql);
+
+                // Update track_exercises.
+                $sql = "UPDATE track_e_exercises SET orig_lp_id = $lpIid 
+                        WHERE c_id = $courseId AND orig_lp_id = $oldId";
+                $connection->query($sql);
+
+                $sql = "UPDATE $tblCLp SET id = iid WHERE c_id = $courseId AND id = $oldId ";
+                $connection->query($sql);
+            }
+        }
+    }
+
+    if ($debug) {
+        error_log('END Fix lp.id lp.iids');
+    }
 }
 
 /**

+ 1 - 8
main/link/link.php

@@ -82,17 +82,14 @@ if ($action === 'addcategory') {
 }
 
 if ($action === 'editlink') {
-    $nameTools = '';
+    $nameTools = get_lang('EditLink');
     $interbreadcrumb[] = ['url' => 'link.php', 'name' => get_lang('Links')];
-    $interbreadcrumb[] = ['url' => '#', 'name' => get_lang('EditLink')];
 }
 
 // Statistics
 Event::event_access_tool(TOOL_LINK);
 
 /*	Action Handling */
-$nameTools = get_lang('Links');
-
 $id = isset($_REQUEST['id']) ? $_REQUEST['id'] : null;
 $scope = isset($_REQUEST['scope']) ? $_REQUEST['scope'] : null;
 $show = isset($_REQUEST['show']) && in_array(trim($_REQUEST['show']), ['all', 'none']) ? $_REQUEST['show'] : '';
@@ -224,10 +221,6 @@ switch ($action) {
 }
 
 Display::display_header($nameTools, 'Links');
-
-/*	Introduction section */
 Display::display_introduction_section(TOOL_LINK);
-
 echo $content;
-
 Display::display_footer();

+ 1 - 1
main/lp/aicc_api.php

@@ -385,7 +385,7 @@ function addListeners(){
     }
     //assign event handlers to objects
     if(lms_lp_type==1 || lms_item_type=='asset'){
-        logit_lms('Dokeos LP or asset',2);
+        logit_lms('Chamilo LP or asset',2);
         // If this path is a Chamilo learnpath, then start manual save
         // when something is loaded in there.
         var myelem = document.getElementById('content_id');

文件差異過大導致無法顯示
+ 195 - 240
main/lp/learnpath.class.php


+ 12 - 10
main/lp/learnpathItem.class.php

@@ -112,7 +112,7 @@ class learnpathItem
             if (empty($course_id)) {
                 $course_id = api_get_course_int_id();
             } else {
-                $course_id = intval($course_id);
+                $course_id = (int) $course_id;
             }
             $sql = "SELECT * FROM $items_table
                     WHERE iid = $id";
@@ -365,7 +365,7 @@ class learnpathItem
         }
         $res = 1;
         if (!empty($this->attempt_id)) {
-            $res = intval($this->attempt_id);
+            $res = (int) $this->attempt_id;
         }
         if (self::DEBUG > 0) {
             error_log(
@@ -503,6 +503,7 @@ class learnpathItem
         }
         $path = $this->get_path();
         $type = $this->get_type();
+
         if (empty($path)) {
             if ($type == 'dir') {
                 return '';
@@ -1077,23 +1078,24 @@ class learnpathItem
                     case 'htm':
                     case 'shtml':
                     case 'css':
-                        $wanted_attributes = [
+                        $wantedAttributes = [
                             'src',
                             'url',
                             '@import',
                             'href',
                             'value',
                         ];
+
                         // Parse it for included resources.
-                        $file_content = file_get_contents($abs_path);
+                        $fileContent = file_get_contents($abs_path);
                         // Get an array of attributes from the HTML source.
                         $attributes = DocumentManager::parse_HTML_attributes(
-                            $file_content,
-                            $wanted_attributes
+                            $fileContent,
+                            $wantedAttributes
                         );
 
                         // Look at 'src' attributes in this file
-                        foreach ($wanted_attributes as $attr) {
+                        foreach ($wantedAttributes as $attr) {
                             if (isset($attributes[$attr])) {
                                 // Find which kind of path these are (local or remote).
                                 $sources = $attributes[$attr];
@@ -3587,7 +3589,7 @@ class learnpathItem
         }
 
         $lp_table = Database::get_course_table(TABLE_LP_MAIN);
-        $lp_id = intval($this->lp_id);
+        $lp_id = (int) $this->lp_id;
         $sql = "SELECT * FROM $lp_table WHERE iid = $lp_id";
         $res = Database::query($sql);
         $accumulateScormTime = 'false';
@@ -4501,7 +4503,7 @@ class learnpathItem
      */
     public function getForumThread($lpCourseId, $lpSessionId = 0)
     {
-        $lpSessionId = intval($lpSessionId);
+        $lpSessionId = (int) $lpSessionId;
         $forumThreadTable = Database::get_course_table(TABLE_FORUM_THREAD);
         $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
 
@@ -4565,7 +4567,7 @@ class learnpathItem
         $threadRepo = $em->getRepository('ChamiloCourseBundle:CForumThread');
         $forumThread = $threadRepo->findOneBy([
             'threadTitle' => "{$this->title} - {$this->db_id}",
-            'forumId' => intval($currentForumId),
+            'forumId' => (int) $currentForumId,
         ]);
 
         if (!$forumThread) {

+ 15 - 12
main/lp/learnpathList.class.php

@@ -34,6 +34,7 @@ class LearnpathList
      * @param bool   $check_publication_dates
      * @param int    $categoryId
      * @param bool   $ignoreCategoryFilter
+     * @param bool   $ignoreLpVisibility      get the list of LPs for reports
      */
     public function __construct(
         $user_id,
@@ -42,7 +43,8 @@ class LearnpathList
         $order_by = null,
         $check_publication_dates = false,
         $categoryId = null,
-        $ignoreCategoryFilter = false
+        $ignoreCategoryFilter = false,
+        $ignoreLpVisibility = false
     ) {
         $course_info = api_get_course_info($course_code);
 
@@ -90,7 +92,7 @@ class LearnpathList
         $categoryFilter = '';
         if ($ignoreCategoryFilter == false) {
             if (!empty($categoryId)) {
-                $categoryId = intval($categoryId);
+                $categoryId = (int) $categoryId;
                 $categoryFilter = " AND lp.categoryId = $categoryId";
             } else {
                 $categoryFilter = " AND (lp.categoryId = 0 OR lp.categoryId IS NULL) ";
@@ -129,11 +131,10 @@ class LearnpathList
                         )
                       ";
             $res2 = Database::query($sql2);
+            $pub = 'i';
             if (Database::num_rows($res2) > 0) {
                 $row2 = Database::fetch_array($res2);
                 $pub = $row2['visibility'];
-            } else {
-                $pub = 'i';
             }
 
             // Check if visible.
@@ -145,14 +146,16 @@ class LearnpathList
             );
 
             // If option is not true then don't show invisible LP to user
-            if ($showBlockedPrerequisite !== true && !api_is_allowed_to_edit()) {
-                $lpVisibility = learnpath::is_lp_visible_for_student(
-                    $row->getId(),
-                    $user_id,
-                    $course_code
-                );
-                if ($lpVisibility === false) {
-                    continue;
+            if ($ignoreLpVisibility === false) {
+                if ($showBlockedPrerequisite !== true && !api_is_allowed_to_edit()) {
+                    $lpVisibility = learnpath::is_lp_visible_for_student(
+                        $row->getId(),
+                        $user_id,
+                        $course_code
+                    );
+                    if ($lpVisibility === false) {
+                        continue;
+                    }
                 }
             }
 

+ 1 - 24
main/lp/lp_add_item.php

@@ -46,30 +46,7 @@ if ($learnPath->get_lp_session_id() != api_get_session_id()) {
     exit;
 }
 
-$htmlHeadXtra[] = '<script>'.
-$learnPath->get_js_dropdown_array()."
-function load_cbo(id) {
-    if (!id) {
-        return false;
-    }
-
-    var cbo = document.getElementById('previous');
-    for(var i = cbo.length - 1; i > 0; i--) {
-        cbo.options[i] = null;
-    }
-
-    var k=0;
-    for(var i = 1; i <= child_name[id].length; i++){
-        var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
-        option.style.paddingLeft = '40px';
-        cbo.options[i] = option;
-        k = i;
-    }
-
-    cbo.options[k].selected = true;
-    $('#previous').selectpicker('refresh');
-}
-
+$htmlHeadXtra[] = '<script>'.$learnPath->get_js_dropdown_array()."
 $(function() {
     if ($('#previous')) {
         if('parent is'+$('#idParent').val()) {

+ 2 - 0
main/lp/lp_admin_view.php

@@ -33,6 +33,7 @@ $_course = api_get_course_info();
 if ((!$is_allowed_to_edit) || ($isStudentView)) {
     error_log('New LP - User not authorized in lp_admin_view.php');
     header('location:lp_controller.php?action=view&lp_id='.$learnpath_id);
+    exit;
 }
 
 if (api_is_in_gradebook()) {
@@ -61,6 +62,7 @@ if (isset($_REQUEST['updateaudio'])) {
     $interbreadcrumb[] = ['url' => '#', 'name' => get_lang('BasicOverview')];
 }
 
+$htmlHeadXtra[] = '<script>'.$learnPath->get_js_dropdown_array().'</script>';
 // Theme calls.
 $show_learn_path = true;
 $lp_theme_css = $learnPath->get_theme();

+ 1 - 1
main/lp/lp_build.php

@@ -44,7 +44,7 @@ if (api_is_in_gradebook()) {
         'name' => get_lang('ToolGradebook'),
     ];
 }
-$interbreadcrumb[] = ['url' => 'lp_controller.php?action=list?'.api_get_cidreq(), 'name' => get_lang('LearningPaths')];
+$interbreadcrumb[] = ['url' => 'lp_controller.php?action=list&'.api_get_cidreq(), 'name' => get_lang('LearningPaths')];
 $interbreadcrumb[] = ['url' => '#', "name" => $learnPath->get_name()];
 
 // Theme calls.

+ 11 - 3
main/lp/lp_controller.php

@@ -199,6 +199,7 @@ if ($refresh == 1) {
 }
 
 if ($debug > 0) {
+    error_log(' $refresh: '.$refresh);
     error_log(' $myrefresh: '.$myrefresh);
 }
 
@@ -245,6 +246,7 @@ if (!empty($lpObject)) {
 }
 if ($debug) {
     error_log('$lp_found: '.$lp_found);
+    error_log('$myrefresh_id: '.$myrefresh_id);
 }
 
 $course_id = api_get_course_int_id();
@@ -342,7 +344,8 @@ if (isset($_SESSION['oLP'])) {
     // Reinitialises array used by javascript to update items in the TOC.
 }
 
-if (isset($_GET['isStudentView']) && $_GET['isStudentView'] == 'true') {
+/*$studentView = api_is_student_view_active();
+if ($studentView) {
     if (isset($_REQUEST['action']) && !in_array($_REQUEST['action'], ['list', 'view', 'view_category'])) {
         if (!empty($_REQUEST['lp_id'])) {
             $_REQUEST['action'] = 'view';
@@ -358,7 +361,7 @@ if (isset($_GET['isStudentView']) && $_GET['isStudentView'] == 'true') {
             $_REQUEST['action'] = 'build';
         }
     }
-}
+}*/
 
 $action = !empty($_REQUEST['action']) ? $_REQUEST['action'] : '';
 
@@ -763,6 +766,7 @@ switch ($action) {
                 $is_success = true;
                 $url = api_get_self().'?action=add_item&type=step&lp_id='.intval($_SESSION['oLP']->lp_id).'&'.api_get_cidreq();
                 header('Location: '.$url);
+                exit;
             }
             if (isset($_GET['view']) && $_GET['view'] == 'build') {
                 require 'lp_move_item.php';
@@ -826,7 +830,7 @@ switch ($action) {
         if (!$lp_found) {
             require 'lp_list.php';
         } else {
-            $_SESSION['oLP']->scorm_export();
+            $_SESSION['oLP']->scormExport();
             exit();
         }
         break;
@@ -1318,12 +1322,14 @@ switch ($action) {
         $url = api_get_self().'?action=add_item&type=step&lp_id='.intval($_SESSION['oLP']->lp_id)."&".api_get_cidreq();
         Display::addFlash(Display::return_message(get_lang('ItemUpdated')));
         header('Location: '.$url);
+        exit;
         break;
     case 'clear_prerequisites':
         $_SESSION['oLP']->clear_prerequisites();
         $url = api_get_self().'?action=add_item&type=step&lp_id='.intval($_SESSION['oLP']->lp_id)."&".api_get_cidreq();
         Display::addFlash(Display::return_message(get_lang('ItemUpdated')));
         header('Location: '.$url);
+        exit;
         break;
     case 'toggle_seriousgame':
         // activate/deactive seriousgame_mode
@@ -1403,6 +1409,7 @@ switch ($action) {
             'type' => 'step',
             'lp_id' => $_SESSION['oLP']->lp_id,
         ]));
+        exit;
 
         break;
     case 'report':
@@ -1443,6 +1450,7 @@ switch ($action) {
             'type' => 'step',
             'lp_id' => $_SESSION['oLP']->lp_id,
         ]));
+        exit;
         break;
     case 'add_final_item':
         if (!$lp_found) {

+ 4 - 2
main/lp/lp_edit.php

@@ -76,7 +76,7 @@ $items = learnpath::getCategoryFromCourseIntoSelect(api_get_course_int_id(), tru
 $form->addElement('select', 'category_id', get_lang('Category'), $items);
 
 // Hide toc frame
-$hide_toc_frame = $form->addElement(
+$form->addElement(
     'checkbox',
     'hide_toc_frame',
     null,
@@ -142,7 +142,7 @@ $defaults['lp_encoding'] = Security::remove_XSS($learnPath->encoding);
 $defaults['lp_name'] = Security::remove_XSS($learnPath->get_name());
 $defaults['lp_author'] = Security::remove_XSS($learnPath->get_author());
 $defaults['hide_toc_frame'] = $hideTableOfContents;
-$defaults['category_id'] = intval($learnPath->getCategoryId());
+$defaults['category_id'] = $learnPath->getCategoryId();
 $defaults['accumulate_scorm_time'] = $learnPath->getAccumulateScormTime();
 
 $expired_on = $learnPath->expired_on;
@@ -241,6 +241,8 @@ if ($enableLpExtraFields) {
     </script>';
 }
 
+$htmlHeadXtra[] = '<script>'.$learnPath->get_js_dropdown_array()."</sript>";
+
 $defaults['publicated_on'] = !empty($publicated_on) && $publicated_on !== '0000-00-00 00:00:00'
     ? api_get_local_time($publicated_on)
     : null;

+ 4 - 32
main/lp/lp_edit_item.php

@@ -21,35 +21,7 @@ api_protect_course_script();
 $learnPath = Session::read('oLP');
 
 /* Header and action code */
-$htmlHeadXtra[] = '
-<script>'.$learnPath->get_js_dropdown_array().
-"
-    function load_cbo(id) {
-        if (!id) {
-            return false;
-        }
-
-        var cbo = document.getElementById('previous');
-
-        for(var i = cbo.length - 1; i > 0; i--) {
-            cbo.options[i] = null;
-        }
-
-        var k=0;
-
-        for(var i = 1; i <= child_name[id].length; i++){
-            var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
-            option.style.paddingLeft = '20px';
-
-            cbo.options[i] = option;
-            k = i;
-        }
-
-        cbo.options[k].selected = true;
-        $('#previous').selectpicker('refresh');
-    }
-".
-'
+$htmlHeadXtra[] = '<script>'.$learnPath->get_js_dropdown_array().'
 $(document).on("ready", function() {
     CKEDITOR.on("instanceReady", function (e) {
         showTemplates("content_lp");
@@ -67,7 +39,7 @@ $submit = isset($_POST['submit_button']) ? $_POST['submit_button'] : null;
 
 if (!$is_allowed_to_edit || $isStudentView) {
     error_log('New LP - User not authorized in lp_edit_item.php');
-    header('location:lp_controller.php?action=view&lp_id='.$learnpath_id);
+    header('location:lp_controller.php?action=view&lp_id='.$learnpath_id.'&'.api_get_cidreq());
     exit;
 }
 // From here on, we are admin because of the previous condition, so don't check anymore.
@@ -157,12 +129,12 @@ echo $learnPath->build_action_menu();
 echo '<div class="row">';
 echo '<div id="lp_sidebar" class="col-md-4">';
 $documentId = isset($_GET['path_item']) ? (int) $_GET['path_item'] : 0;
-$documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
+$documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), false, null, true);
 if (empty($documentInfo)) {
     // Try with iid
     $table = Database::get_course_table(TABLE_DOCUMENT);
     $sql = "SELECT path FROM $table
-            WHERE c_id = $course_id AND iid = $documentId";
+            WHERE c_id = $course_id AND iid = $documentId AND path NOT LIKE '%_DELETED_%'";
     $res_doc = Database::query($sql);
     $path_file = Database::result($res_doc, 0, 0);
 } else {

+ 6 - 5
main/lp/lp_final_item.php

@@ -47,13 +47,14 @@ if ($checker) {
     if (empty($userServiceSale)) {
         // Instance a new template : No page tittle, No header, No footer
         $tpl = new Template(null, false, false);
+        $url = api_get_path(WEB_PLUGIN_PATH).'buycourses/src/service_catalog.php';
         $content = sprintf(
             Display::return_message(
-                $plugin->get_lang('IfYouWantToGetTheCertificateAndOrSkillsAsociatedToThisCourseYouNeedToBuyTheCertificateServiceYouCanGoToServiceCatalogClickingHere'),
+                get_lang('IfYouWantToGetTheCertificateAndOrSkillsAsociatedToThisCourseYouNeedToBuyTheCertificateServiceYouCanGoToServiceCatalogClickingHere'),
                 'normal',
                 false
             ),
-            api_get_path(WEB_PLUGIN_PATH).'buycourses/src/service_catalog.php'
+            '<a href="'.$url.'">'.$url.'</a>'
         );
         $tpl->assign('content', $content);
         $tpl->display_blank_template();
@@ -133,8 +134,8 @@ if ($accessGranted == false) {
 
         if ($link) {
             $cat = new Category();
-            $courseId = CourseManager::get_course_by_category($categoryId);
-            $show_message = $cat->show_message_resource_delete($courseId);
+            $catCourseCode = CourseManager::get_course_by_category($categoryId);
+            $show_message = $cat->show_message_resource_delete($catCourseCode);
 
             if ($show_message == '') {
                 if (!api_is_allowed_to_edit() && !api_is_excluded_user_type()) {
@@ -257,7 +258,7 @@ function generateLPFinalItemTemplateBadgeLinks($userId, $courseId, $sessionId =
                 <div class='row'>
                     <div class='col-md-2 col-xs-4'>
                         <div class='thumbnail'>
-                          <img class='skill-badge-img' src='".$skill->getWebIconPath()."' >
+                          <img class='skill-badge-img' src='".Skill::getWebIconPath($skill)."' >
                         </div>
                     </div>
                     <div class='col-md-8 col-xs-8'>

+ 2 - 2
main/lp/lp_list.php

@@ -55,7 +55,7 @@ $sessionId = api_get_session_id();
 $subscriptionSettings = learnpath::getSubscriptionSettings();
 
 /* Introduction section (editable by course admins) */
-$introductionSection = Display::return_introduction_section(
+$introduction = Display::return_introduction_section(
     TOOL_LEARNPATH,
     [
         'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH)
@@ -936,7 +936,7 @@ $template->assign('is_invitee', api_is_invitee());
 $template->assign('actions', $actions);
 $template->assign('categories', $categories);
 $template->assign('message', $message);
-$template->assign('introduction_section', $introductionSection);
+$template->assign('introduction', $introduction);
 $template->assign('data', $data);
 $template->assign('lp_is_shown', $lpIsShown);
 $template->assign('filtered_category', $filteredCategoryId);

+ 3 - 31
main/lp/lp_move_item.php

@@ -22,39 +22,14 @@ $learnPath = Session::read('oLP');
 /* Header and action code */
 $htmlHeadXtra[] = '<script>'.
 $learnPath->get_js_dropdown_array().
-"
-    function load_cbo(id) {
-        if (!id) {
-            return false;
-        }
-
-        var cbo = document.getElementById('previous');
-
-        for(var i = cbo.length - 1; i > 0; i--) {
-            cbo.options[i] = null;
-        }
-
-        var k=0;
-
-        for(var i = 1; i <= child_name[id].length; i++) {
-            cbo.options[i] = new Option(child_name[id][i - 1], child_value[id][i - 1]);
-            k=i;
-        }
-
-        cbo.options[k].selected = true;
-        $('#previous').selectpicker('refresh');
-    }
-".
-"\n".
 '$().ready(function() {'."\n".
   'if ($(\'#previous\')) {'."\n".
     'if(\'parent is\'+$(\'#idParent\').val()) {'.
       'load_cbo($(\'#idParent\').val());'."\n".
   '}}'."\n".
-'});</script>'."\n";
+'});</script>';
 
 /* Constants and variables */
-
 $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
 
 $isStudentView = isset($_REQUEST['isStudentView']) ? (int) $_REQUEST['isStudentView'] : '';
@@ -64,14 +39,11 @@ $submit = isset($_POST['submit_button']) ? $_POST['submit_button'] : '';
 /* MAIN CODE */
 if ((!$is_allowed_to_edit) || ($isStudentView)) {
     header('location:lp_controller.php?action=view&lp_id='.$learnpath_id);
+    exit;
 }
 // From here on, we are admin because of the previous condition, so don't check anymore.
 
 $course_id = api_get_course_int_id();
-$sql = "SELECT * FROM $tbl_lp WHERE iid = $learnpath_id";
-
-$result = Database::query($sql);
-$therow = Database::fetch_array($result);
 
 /*
     Course admin section
@@ -91,7 +63,7 @@ $interbreadcrumb[] = [
 ];
 $interbreadcrumb[] = [
     'url' => api_get_self()."?action=build&lp_id=$learnpath_id&".api_get_cidreq(),
-    'name' => stripslashes("{$therow['name']}"),
+    'name' => stripslashes($learnPath->get_name()),
 ];
 $interbreadcrumb[] = [
     'url' => api_get_self()."?action=add_item&type=step&lp_id=$learnpath_id&".api_get_cidreq(),

+ 4 - 4
main/lp/lp_stats.php

@@ -16,16 +16,16 @@ if (!isset($origin)) {
     $origin = 'learnpath';
 }
 
-$sessionId = isset($_GET['id_session']) ? intval($_GET['id_session']) : api_get_session_id();
+$sessionId = isset($_GET['id_session']) ? (int) $_GET['id_session'] : api_get_session_id();
 $courseCode = isset($_GET['course']) ? $_GET['course'] : api_get_course_id();
-$userId = isset($_GET['student_id']) ? intval($_GET['student_id']) : api_get_user_id();
+$userId = isset($_GET['student_id']) ? (int) $_GET['student_id'] : api_get_user_id();
 $lpId = isset($_GET['lp_id']) ? $_GET['lp_id'] : null;
 $lpItemId = isset($_GET['lp_item_id']) ? $_GET['lp_item_id'] : null;
 $extendId = isset($_GET['extend_id']) ? $_GET['extend_id'] : null;
 $extendAttemptId = isset($_GET['extend_attempt_id']) ? $_GET['extend_attempt_id'] : null;
 $extendedAttempt = isset($_GET['extend_attempt']) ? $_GET['extend_attempt'] : null;
 $extendedAll = isset($_GET['extend_all']) ? $_GET['extend_all'] : null;
-$export = isset($_GET['export']) && $_GET['export'] == 'csv' ? true : false;
+$export = isset($_GET['export']) && $_GET['export'] === 'csv' ? true : false;
 $allowExtend = isset($_GET['allow_extend']) ? $_GET['allow_extend'] : 1;
 
 $lpReportType = api_get_setting('lp_show_reduced_report');
@@ -53,7 +53,7 @@ $output = Tracking::getLpStats(
 // Origin = tracking means that teachers see that info in the Reporting tool
 if ($origin != 'tracking') {
     Display::display_reduced_header();
-    $output .= "</body></html>";
+    $output .= '</body></html>';
 }
 
 return $output;

+ 2 - 0
main/lp/lp_subscribe_users_to_category.php

@@ -53,6 +53,7 @@ $message = Display::return_message(get_lang('UserLpCategorySubscriptionDescripti
 // Building the form for Groups
 $form = new FormValidator('lp_edit', 'post', $url);
 $form->addElement('hidden', 'group_form', 1);
+$form->addLabel('', $message);
 
 // Group list
 $groupList = \CourseManager::get_group_list_of_course(
@@ -127,6 +128,7 @@ foreach ($subscribedUsersInCategory as $item) {
 // Building the form for Users
 $formUsers = new FormValidator('lp_edit', 'post', $url);
 $formUsers->addElement('hidden', 'user_form', 1);
+$formUsers->addLabel('', $message);
 
 $userMultiSelect = $formUsers->addElement(
     'advmultiselect',

+ 6 - 6
main/lp/lp_view.php

@@ -35,7 +35,7 @@ $show_learnpath = true;
 
 api_protect_course_script();
 
-$lp_id = !empty($_GET['lp_id']) ? intval($_GET['lp_id']) : 0;
+$lp_id = !empty($_GET['lp_id']) ? (int) $_GET['lp_id'] : 0;
 $sessionId = api_get_session_id();
 $course_code = api_get_course_id();
 $course_id = api_get_course_int_id();
@@ -60,8 +60,8 @@ $visibility = api_get_item_visibility(
     $sessionId
 );
 
-if (!api_is_allowed_to_edit(false, true, false, false) &&
-    intval($visibility) == 0
+if ($visibility === 0 &&
+    !api_is_allowed_to_edit(false, true, false, false)
 ) {
     api_not_allowed(true);
 }
@@ -113,7 +113,7 @@ if (!$is_allowed_to_edit) {
     }
 }
 
-$platform_theme = api_get_setting('stylesheets'); // Platform's css.
+$platform_theme = api_get_setting('stylesheets');
 $my_style = $platform_theme;
 $ajaxUrl = api_get_path(WEB_AJAX_PATH).'lp.ajax.php?a=get_item_prerequisites&'.api_get_cidreq();
 $htmlHeadXtra[] = '<script>
@@ -300,9 +300,9 @@ if (!empty($_REQUEST['exeId']) &&
     $TBL_TRACK_EXERCICES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
     $TBL_LP_ITEM_VIEW = Database::get_course_table(TABLE_LP_ITEM_VIEW);
     $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
-    $safe_item_id = intval($_GET['lp_item_id']);
+    $safe_item_id = (int) $_GET['lp_item_id'];
     $safe_id = $lp_id;
-    $safe_exe_id = intval($_REQUEST['exeId']);
+    $safe_exe_id = (int) $_REQUEST['exeId'];
 
     if ($safe_id == strval(intval($safe_id)) &&
         $safe_item_id == strval(intval($safe_item_id))

+ 2 - 2
main/lp/lp_view_item.php

@@ -48,8 +48,8 @@ if (empty($lp)) {
 }
 
 $mode = isset($_REQUEST['mode']) ? $_REQUEST['mode'] : 'fullpage';
-if (isset($lp) && isset($_GET['id'])) {
-    $lp->current = intval($_GET['id']);
+if (isset($_SESSION['oLP']) && isset($_GET['id'])) {
+    $_SESSION['oLP']->current = intval($_GET['id']);
 }
 $this_section = SECTION_COURSES;
 /* Header and action code */

+ 7 - 4
main/lp/openoffice_document.class.php

@@ -67,6 +67,9 @@ abstract class OpenofficeDocument extends learnpath
             $this->file_path = $this->created_dir.'/'.api_replace_dangerous_char($file['name']);
         }
 
+        $dirMode = api_get_permissions_for_new_directories();
+        $fileMode = api_get_permissions_for_new_files();
+
         //var_dump($this->file_name, $this->file_path, $this->base_work_dir, $this->created_dir);
 
         /*
@@ -123,9 +126,9 @@ abstract class OpenofficeDocument extends learnpath
             // Call to the function implemented by child.
             $cmd .= $this->add_command_parameters();
             // To allow openoffice to manipulate docs.
-            @chmod($this->base_work_dir, 0777);
-            @chmod($this->base_work_dir.$this->created_dir, 0777);
-            @chmod($this->base_work_dir.$this->file_path, 0777);
+            @chmod($this->base_work_dir, $dirMode);
+            @chmod($this->base_work_dir.$this->created_dir, $dirMode);
+            @chmod($this->base_work_dir.$this->file_path, $fileMode);
 
             $locale = $this->original_locale; // TODO: Improve it because we're not sure this locale is present everywhere.
             putenv('LC_ALL='.$locale);
@@ -163,7 +166,7 @@ abstract class OpenofficeDocument extends learnpath
                 foreach ($result['images'] as $image => $img_data) {
                     $image_path = $this->base_work_dir.$this->created_dir;
                     @file_put_contents($image_path.'/'.$image, base64_decode($img_data));
-                    @chmod($image_path.'/'.$image, 0777);
+                    @chmod($image_path.'/'.$image, $fileMode);
                 }
             }
 

+ 279 - 214
main/lp/packaging/assets/api_wrapper.js

@@ -6,11 +6,13 @@
  * @author	Yannick Warnier  - inspired by the ADLNet documentation on SCORM content-side API
  * @package scorm.js
  */
+
 /**
  * Initialisation of the SCORM API section.
  * Find the SCO functions (startTimer, computeTime, etc in the second section)
  * Find the Chamilo-proper functions (checkAnswers, etc in the third section)
  */
+
 var _debug = true;
 var findAPITries = 0;
 var _apiHandle = null; //private variable
@@ -27,6 +29,8 @@ var _InvalidSetValue = 402;
 var _ElementIsReadOnly = 403;
 var _ElementIsWriteOnly = 404;
 var _IncorrectDataType = 405;
+var startTime;
+var exitPageStatus;
 
 /**
  * Gets the API handle right into the local API object and ensure there is only one.
@@ -78,11 +82,12 @@ function getAPI()
     }
     return MyAPI;
 }
+
 /**
  * Handles error codes (prints the error if it has a description)
  * @return	int	Error code from LMS's API
  */
-function ErrorHandler()
+function errorHandler()
 {
     if (API == null) {
         alert("Unable to locate the LMS's API. Cannot determine LMS error code");
@@ -96,19 +101,31 @@ function ErrorHandler()
                 errDescription += "\n";
                 errDescription += api.LMSGetDiagnostic(null);
             }
-            console.log(errDescription);
+            addDebug(errDescription);
         } else {
             var errDescription = API.LMSGetErrorString(errCode);
             if (_debug) {
                 errDescription += "\n";
                 errDescription += api.LMSGetDiagnostic(null);
             }
-            console.log(errDescription);
+            addDebug(errDescription);
         }
     }
     return errCode;
 }
 
+function addDebug(message) {
+    if (_debug && window.console) {
+        console.log(message);
+    }
+}
+
+function addDebugTable(message) {
+    if (_debug && window.console) {
+        console.table(message);
+    }
+}
+
 /**
  * Calls the LMSInitialize method of the LMS's API object
  * @return string	The string value of the LMS returned value or false if error (should be "true" otherwise)
@@ -119,9 +136,10 @@ function doLMSInitialize()
         alert(errMsgLocate + "\nLMSInitialize failed");
         return false;
     }
+
     var result = API.LMSInitialize("");
     if (result.toString() != "true") {
-        var err = ErrorHandler();
+        var err = errorHandler();
     }
     return result.toString();
 }
@@ -138,7 +156,7 @@ function doLMSFinish()
     } else {
         var result = API.LMSFinish('');
         if (result.toString() != "true") {
-            var err = ErrorHandler();
+            var err = errorHandler();
         }
     }
     return result.toString();
@@ -152,16 +170,18 @@ function doLMSGetValue(name)
 {
     if (API == null) {
         alert(errMsgLocate + "\nLMSGetValue was not successful.");
-        return "";
+        return '';
     } else {
         var value = API.LMSGetValue(name);
         var errCode = API.LMSGetLastError().toString();
         if (errCode != _NoError) {
             // an error was encountered so display the error description
             var errDescription = API.LMSGetErrorString(errCode);
-            alert("LMSGetValue(" + name + ") failed. \n" + errDescription);
-            return "";
+            addDebug("LMSGetValue(" + name + ") failed. \n" + errDescription)
+            return '';
         }
+
+        return value.toString();
     }
 }
 
@@ -179,7 +199,7 @@ function doLMSSetValue(name, value)
     } else {
         var result = API.LMSSetValue(name, value);
         if (result.toString() != "true") {
-            var err = ErrorHandler();
+            var err = errorHandler();
         }
     }
     return;
@@ -196,7 +216,7 @@ function doLMSCommit()
     } else {
         var result = API.LMSCommit("");
         if (result != "true") {
-            var err = ErrorHandler();
+            var err = errorHandler();
         }
     }
     return result.toString();
@@ -224,7 +244,7 @@ function doLMSGetErrorString(errorCode)
         alert(errMsgLocate + "\nLMSGetErrorString was not successful.");
     }
 
-   return API.LMSGetErrorString(errorCode).toString();
+    return API.LMSGetErrorString(errorCode).toString();
 }
 
 /**
@@ -236,15 +256,9 @@ function doLMSGetDiagnostic(errorCode)
         alert(errMsgLocate + "\nLMSGetDiagnostic was not successful.");
     }
 
-   return API.LMSGetDiagnostic(errorCode).toString();
+    return API.LMSGetDiagnostic(errorCode).toString();
 }
 
-/**
- * Second section. The SCO functions are located here (handle time and score messaging to SCORM API)
- * Initialisation
- */
-var startTime;
-var exitPageStatus;
 /**
  * Initialise page values
  */
@@ -379,18 +393,10 @@ function unloadPage(status)
 {
     if (!exitPageStatus)
     {
-           // doQuit( status );
+        // doQuit( status );
     }
 }
-/**
- * Third section - depending on Chamilo - check answers and set score
- */
-var questions = new Array();
-var questions_answers = new Array();
-var questions_answers_correct = new Array();
-var questions_types = new Array();
-var questions_score_max = new Array();
-var questions_answers_ponderation = new Array();
+
 /**
  * Checks the answers on the test formular page
  */
@@ -400,13 +406,10 @@ function checkAnswers(interrupted)
     var status = 'not attempted';
     var scoreMax = 0;
 
-    if (_debug) {
-        console.log('questions_answers_correct:');
-        console.log(questions_answers_correct);
-    }
+    addDebug('Number of questions: '+ questions.length);
 
     for (var i=0; i < questions.length; i++) {
-        if (questions[i] != undefined && questions[i] != null){
+        if (questions[i] != undefined && questions[i] != null) {
             var idQuestion = questions[i];
             var type = questions_types[idQuestion];
             var interactionScore = 0;
@@ -414,69 +417,45 @@ function checkAnswers(interrupted)
             var interactionCorrectResponses = '';
             var interactionType = '';
 
-            if (_debug) {
-                console.log('Type: ' +type);
-                console.log('idQuestion: ' +idQuestion);
-                console.log('questions_answers: ');
-                console.log(questions_answers[idQuestion]);
-                console.log('questions_answers_ponderation: ');
-                console.log(questions_answers_ponderation[idQuestion]);
-                console.log('questions_answers_correct: ');
-                console.log(questions_answers_correct[idQuestion]);
-            }
-
-            if (type == 'mcma') {
-                interactionType = 'choice';
-                var myScore = 0;
-                for(var j=0; j<questions_answers[idQuestion].length;j++) {
-                    var idAnswer = questions_answers[idQuestion][j];
-                    var answer = document.getElementById('question_'+(idQuestion)+'_multiple_'+(idAnswer));
-                    if (answer.checked) {
-                        interactionAnswers += idAnswer+'__|';// changed by isaac flores
-                        myScore += questions_answers_ponderation[idQuestion][idAnswer];
-                    }
-                }
-                interactionScore = myScore;
-                scoreMax += questions_score_max[idQuestion];
-                if (_debug) {
-                    console.log("Score: "+myScore);
-                }
-            } else if (type == 'mcua') {
-                interactionType = 'choice';
-                var myScore = 0;
-                for (var j=0; j<questions_answers[idQuestion].length;j++) {
-                    var idAnswer = questions_answers[idQuestion][j];
-                    var answer = document.getElementById('question_'+(idQuestion)+'_unique_'+(idAnswer));
-                    if (answer.checked) {
-                        interactionAnswers += idAnswer;
-                        if (_debug) {
-                            console.log("idAnswer: "+idAnswer);
-                            console.log("questions_answers_correct: "+questions_answers_correct[idQuestion][idAnswer]);
-                        }
-                        if (questions_answers_correct[idQuestion][idAnswer] == idAnswer) {
-                            if (questions_answers_ponderation[idQuestion][idAnswer]) {
-                                myScore += questions_answers_ponderation[idQuestion][idAnswer];
-                            } else {
-                                myScore++;
-                            }
+            addDebug('idQuestion: ' +idQuestion + ', Type: ' +type);
+            addDebug('questions_answers: ');
+            addDebugTable(questions_answers[idQuestion]);
+            addDebug('questions_answers_ponderation: ');
+            addDebugTable(questions_answers_ponderation[idQuestion]);
+            addDebug('questions_answers_correct: ');
+            addDebugTable(questions_answers_correct[idQuestion]);
+
+            switch (type) {
+                case 'mcma':
+                    interactionType = 'choice';
+                    var myScore = 0;
+                    for(var j = 0; j< questions_answers[idQuestion].length;j++) {
+                        var idAnswer = questions_answers[idQuestion][j];
+                        var answer = document.getElementById('question_'+(idQuestion)+'_multiple_'+(idAnswer));
+                        if (answer.checked) {
+                            interactionAnswers += idAnswer+'__|';// changed by isaac flores
+                            myScore += questions_answers_ponderation[idQuestion][idAnswer];
                         }
                     }
-                }
-                if (_debug) {
-                    console.log("Score: "+myScore);
-                }
-                interactionScore = myScore;
-                scoreMax += questions_score_max[idQuestion];
-            } else if (type == 'tf') {
-                interactionType = 'true-false';
-                var myScore = 0;
-                for (var j = 0; j < questions_answers[idQuestion].length; j++) {
-                    var idAnswer = questions_answers[idQuestion][j];
-                    var answer = document.getElementById('question_' + idQuestion + '_tf_' + (idAnswer));
-                    if (answer.checked.value) {
-                        interactionAnswers += idAnswer;
-                        for (k = 0; k < questions_answers_correct[idQuestion].length; k++) {
-                            if (questions_answers_correct[idQuestion][k] == idAnswer) {
+                    interactionScore = myScore;
+                    scoreMax += questions_score_max[idQuestion];
+                    addDebug("Score: "+myScore);
+                    break;
+                case 'mcua':
+                    interactionType = 'choice';
+                    var myScore = 0;
+                    for (var j = 0; j<questions_answers[idQuestion].length; j++) {
+                        var idAnswer = questions_answers[idQuestion][j];
+                        var elementId = 'question_'+(idQuestion)+'_unique_'+(idAnswer);
+                        var answer = document.getElementById(elementId);
+                        if (answer.checked) {
+                            addDebug('Element id # "'+ elementId +'" was checked');
+                            interactionAnswers += idAnswer;
+                            addDebug("List of correct answers: "+questions_answers_correct[idQuestion]);
+                            addDebug('Score for this answer: ' + questions_answers_ponderation[idQuestion][idAnswer]);
+                            addDebug("idAnswer: "+idAnswer);
+                            addDebug("Option selected: "+questions_answers_correct[idQuestion][idAnswer]);
+                            if (questions_answers_correct[idQuestion][idAnswer] == 1) {
                                 if (questions_answers_ponderation[idQuestion][idAnswer]) {
                                     myScore += questions_answers_ponderation[idQuestion][idAnswer];
                                 } else {
@@ -485,132 +464,166 @@ function checkAnswers(interrupted)
                             }
                         }
                     }
-                }
-                if (_debug) {
-                    console.log("Score: "+myScore);
-                }
-                interactionScore = myScore;
-                scoreMax += questions_score_max[idQuestion];
-            } else if (type == 'fib') {
-                interactionType = 'fill-in';
-                var myScore = 0;
-                for (var j = 0; j < questions_answers[idQuestion].length; j++) {
-                    var idAnswer = questions_answers[idQuestion][j];
-                    var answer = document.getElementById('question_'+(idQuestion)+'_fib_'+(idAnswer));
-                    if (answer.value) {
-                        interactionAnswers += answer.value + '__|';//changed by isaac flores
-                        for (k = 0; k < questions_answers_correct[idQuestion].length; k++) {
-                            if (questions_answers_correct[idQuestion][k] == answer.value) {
-                                if (questions_answers_ponderation[idQuestion][idAnswer]) {
-                                    myScore += questions_answers_ponderation[idQuestion][idAnswer];
-                                } else {
-                                    myScore++;
+                    addDebug("Score: "+myScore);
+                    interactionScore = myScore;
+                    scoreMax += questions_score_max[idQuestion];
+                    break;
+                case 'tf':
+                    interactionType = 'true-false';
+                    var myScore = 0;
+                    for (var j = 0; j < questions_answers[idQuestion].length; j++) {
+                        var idAnswer = questions_answers[idQuestion][j];
+                        var answer = document.getElementById('question_' + idQuestion + '_tf_' + (idAnswer));
+                        if (answer.checked.value) {
+                            interactionAnswers += idAnswer;
+                            for (k = 0; k < questions_answers_correct[idQuestion].length; k++) {
+                                if (questions_answers_correct[idQuestion][k] == idAnswer) {
+                                    if (questions_answers_ponderation[idQuestion][idAnswer]) {
+                                        myScore += questions_answers_ponderation[idQuestion][idAnswer];
+                                    } else {
+                                        myScore++;
+                                    }
                                 }
                             }
                         }
                     }
-                }
-                if (_debug) {
-                    console.log("Score: "+myScore);
-                }
-                interactionScore = myScore;
-                scoreMax += questions_score_max[idQuestion];
-            } else if (type == 'matching') {
-                interactionType = 'matching';
-                var myScore = 0;
-                for (var j = 0; j < questions_answers[idQuestion].length; j++) {
-                    var idAnswer = questions_answers[idQuestion][j];
-                    var answer = document.getElementById('question_' + (idQuestion) + '_matching_' + (idAnswer));
-                    if (answer && answer.value) {
-                        interactionAnswers += answer.value + '__|';//changed by isaac flores
-                        for (k = 0; k < questions_answers_correct[idQuestion].length; k++) {
-                            var left = questions_answers_correct[idQuestion][k][0];
-                            var right = questions_answers_correct[idQuestion][k][1];
-                            if (left == idAnswer && right == answer.value) {
-                                if (questions_answers_ponderation[idQuestion][idAnswer]) {
-                                    myScore += questions_answers_ponderation[idQuestion][idAnswer];
-                                } else {
-                                    myScore++;
+                    addDebug("Score: "+  myScore);
+                    interactionScore = myScore;
+                    scoreMax += questions_score_max[idQuestion];
+                    break;
+                case 'fib':
+                    interactionType = 'fill-in';
+                    var myScore = 0;
+                    for (var j = 0; j < questions_answers[idQuestion].length; j++) {
+                        var idAnswer = questions_answers[idQuestion][j];
+                        var answer = document.getElementById('question_'+(idQuestion)+'_fib_'+(idAnswer));
+                        if (answer.value) {
+                            interactionAnswers += answer.value + '__|';//changed by isaac flores
+                            for (k = 0; k < questions_answers_correct[idQuestion].length; k++) {
+                                if (questions_answers_correct[idQuestion][k] == answer.value) {
+                                    if (questions_answers_ponderation[idQuestion][idAnswer]) {
+                                        myScore += questions_answers_ponderation[idQuestion][idAnswer];
+                                    } else {
+                                        myScore++;
+                                    }
                                 }
                             }
                         }
                     }
-                }
-                if (_debug) {
-                    console.log("Score: "+myScore);
-                }
-                interactionScore = myScore;
-                scoreMax += questions_score_max[idQuestion];
-            } else if (type == 'free') {
-                //ignore for now as a score cannot be given
-                interactionType = 'free';
-                var answer = document.getElementById('question_'+(idQuestion)+'_free');
-                if (answer && answer.value) {
-                    interactionAnswers += answer.value
-                }
-
-                //interactionScore = questions_score_max[idQuestion];
-                interactionScore = 0;
-                scoreMax += questions_score_max[idQuestion];
-
-                //interactionAnswers = document.getElementById('question_'+(idQuestion)+'_free').value;
-                //correct responses work by pattern, see SCORM Runtime Env Doc
-                //interactionCorrectResponses += questions_answers_correct[idQuestion].toString();
-            } else if (type == 'hotspot') {
-                interactionType = 'sequencing';
-                interactionScore = 0;
-                //if(question_score && question_score[idQuestion]){
-                //	interactionScore = question_score[idQuestion];
-                //} //else, 0
-                //interactionAnswers = document.getElementById('question_'+(idQuestion)+'_free').innerHTML;
-                //correct responses work by pattern, see SCORM Runtime Env Doc
-                //for(k=0;k<questions_answers_correct[idQuestion].length;k++)
-                //{
-                //	interactionCorrectResponses += questions_answers_correct[idQuestion][k].toString()+',';
-                //}
-            } else if (type == 'exact') {
-                interactionType = 'exact';
-                interactionScore = 0;
-                var real_answers = new Array();
-                for (var j = 0; j < questions_answers[idQuestion].length; j++) {
-                    var idAnswer = questions_answers[idQuestion][j];
-                    var answer = document.getElementById('question_' + (idQuestion) + '_exact_' + (idAnswer));
-
-                    if (answer.checked == true) {
-                        interactionAnswers += idAnswer+', ';
-                        if (questions_answers_correct[idQuestion][idAnswer] != 0) {
-                            real_answers[j] = true;
-                        } else {
-                            real_answers[j] = false;
+                    addDebug("Score: "+myScore);
+                    interactionScore = myScore;
+                    scoreMax += questions_score_max[idQuestion];
+                    break;
+                case 'matching':
+                    interactionType = 'matching';
+                    var myScore = 0;
+                    addDebug("List of correct answers: ");
+                    console.log(questions_answers_correct[idQuestion]);
+                    for (var j = 0; j < questions_answers[idQuestion].length; j++) {
+                        var idAnswer = questions_answers[idQuestion][j];
+                        var elementId = 'question_' + (idQuestion) + '_matching_' + (idAnswer);
+
+                        addDebug("---------idAnswer #"+idAnswer+'------------------');
+                        addDebug("Checking element #"+elementId);
+
+                        var answer = document.getElementById(elementId);
+
+                        if (answer && answer.value) {
+                            interactionAnswers += answer.value + '__|';//changed by isaac flores
+                            for (k = 0; k < questions_answers_correct[idQuestion].length; k++) {
+                                var left = questions_answers_correct[idQuestion][k][0];
+                                var right = questions_answers_correct[idQuestion][k][1];
+                                addDebug('Left ' + left);
+                                addDebug('Right ' + right);
+                                addDebug('answer.value ' + answer.value);
+
+                                if (right == idAnswer && left == answer.value) {
+                                    addDebug('Score for this answer: ' + questions_answers_ponderation[idQuestion][idAnswer]);
+                                    if (questions_answers_ponderation[idQuestion][idAnswer]) {
+                                        myScore += questions_answers_ponderation[idQuestion][idAnswer];
+                                    } else {
+                                        // myScore++;
+                                    }
+                                }
+                            }
                         }
-                    } else {
-                        if (questions_answers_correct[idQuestion][idAnswer] != 0) {
-                            real_answers[j] = false;
+                        addDebug("Partial score: "+myScore);
+                        addDebug("--------- end --- idAnswer #"+idAnswer+'------------------');
+                    }
+                    addDebug("Score: "+myScore);
+                    interactionScore = myScore;
+                    scoreMax += questions_score_max[idQuestion];
+                    break;
+                case 'free':
+                    //ignore for now as a score cannot be given
+                    interactionType = 'free';
+                    var answer = document.getElementById('question_'+(idQuestion)+'_free');
+                    if (answer && answer.value) {
+                        interactionAnswers += answer.value
+                    }
+
+                    //interactionScore = questions_score_max[idQuestion];
+                    interactionScore = 0;
+                    scoreMax += questions_score_max[idQuestion];
+
+                    //interactionAnswers = document.getElementById('question_'+(idQuestion)+'_free').value;
+                    //correct responses work by pattern, see SCORM Runtime Env Doc
+                    //interactionCorrectResponses += questions_answers_correct[idQuestion].toString();
+                    break;
+                case 'hotspot':
+                    interactionType = 'sequencing';
+                    interactionScore = 0;
+                    //if(question_score && question_score[idQuestion]){
+                    //	interactionScore = question_score[idQuestion];
+                    //} //else, 0
+                    //interactionAnswers = document.getElementById('question_'+(idQuestion)+'_free').innerHTML;
+                    //correct responses work by pattern, see SCORM Runtime Env Doc
+                    //for(k=0;k<questions_answers_correct[idQuestion].length;k++)
+                    //{
+                    //	interactionCorrectResponses += questions_answers_correct[idQuestion][k].toString()+',';
+                    //}
+                    break;
+                case 'exact':
+                    interactionType = 'exact';
+                    interactionScore = 0;
+                    var real_answers = new Array();
+                    for (var j = 0; j < questions_answers[idQuestion].length; j++) {
+                        var idAnswer = questions_answers[idQuestion][j];
+                        var answer = document.getElementById('question_' + (idQuestion) + '_exact_' + (idAnswer));
+
+                        if (answer.checked == true) {
+                            interactionAnswers += idAnswer+', ';
+                            if (questions_answers_correct[idQuestion][idAnswer] != 0) {
+                                real_answers[j] = true;
+                            } else {
+                                real_answers[j] = false;
+                            }
                         } else {
-                            real_answers[j] = true;
+                            if (questions_answers_correct[idQuestion][idAnswer] != 0) {
+                                real_answers[j] = false;
+                            } else {
+                                real_answers[j] = true;
+                            }
                         }
                     }
-                }
 
-                var final_answer = true;
-                for (var z = 0; z < real_answers.length; z++) {
-                    if (real_answers[z] == false) {
-                        final_answer = false;
+                    var final_answer = true;
+                    for (var z = 0; z < real_answers.length; z++) {
+                        if (real_answers[z] == false) {
+                            final_answer = false;
+                        }
+                    }
+                    interactionScore = 0;
+                    addDebug(real_answers);
+                    if (final_answer) {
+                        //getting only the first score where we save the weight of all the question
+                        interactionScore = questions_answers_ponderation[idQuestion][1];
                     }
-                }
-                interactionScore = 0;
-                console.log(real_answers);
-                if (final_answer) {
-                     //getting only the first score where we save the weight of all the question
-                     interactionScore = questions_answers_ponderation[idQuestion][1];
-                }
-                if (_debug) {
-                    console.log("Score: "+interactionScore);
-                }
-                scoreMax += questions_score_max[idQuestion];
+                    addDebug("Score: "+interactionScore);
+                    scoreMax += questions_score_max[idQuestion];
+                    break;
             }
             tmpScore += interactionScore;
-
             doLMSSetValue('cmi.interactions.'+idQuestion+'.id', 'Q'+idQuestion);
             doLMSSetValue('cmi.interactions.'+idQuestion+'.type', interactionType);
             doLMSSetValue('cmi.interactions.'+idQuestion+'.student_response', interactionAnswers);
@@ -621,29 +634,81 @@ function checkAnswers(interrupted)
     doLMSSetValue('cmi.core.score.max', scoreMax);
     doLMSSetValue('cmi.core.score.raw', tmpScore);
 
-    //get status
+    // Get status
     var mastery_score = doLMSGetValue('cmi.student_data.mastery_score');
     if (mastery_score <= 0) {
         mastery_score = (scoreMax * 0.80);
     }
-    if (tmpScore > mastery_score) {
+    if (tmpScore >= mastery_score) {
         status = 'passed';
     } else {
         status = 'failed';
     }
 
-    if (_debug) {
-        console.log('student_score: ' + tmpScore);
-        console.log('mastery_score: ' + mastery_score);
-        console.log('cmi.core.score.max: ' + scoreMax);
-        console.log('cmi.core.lesson_status: ' + status);
-    }
-
+    addDebug('student_score: ' + tmpScore);
+    addDebug('mastery_score: ' + mastery_score);
+    addDebug('cmi.core.score.max: ' + scoreMax);
+    addDebug('cmi.core.lesson_status: ' + status);
     doLMSSetValue('cmi.core.lesson_status', status);
-
     if (interrupted && (status != 'completed') && (status != 'passed')) {
         doLMSSetValue('cmi.core.exit', 'suspended');
     }
 
     return false; //do not submit the form
 }
+
+(function($){
+    //Shuffle all rows, while keeping the first column
+    //Requires: Shuffle
+    $.fn.shuffleRows = function(){
+        return this.each(function(){
+            var main = $(/table/i.test(this.tagName) ? this.tBodies[0] : this);
+            var firstElem = [], counter=0;
+            main.children().each(function(){
+                firstElem.push(this.firstChild);
+            });
+            main.shuffle();
+            main.children().each(function(){
+                this.insertBefore(firstElem[counter++], this.firstChild);
+            });
+        });
+    }
+    /* Shuffle is required */
+    $.fn.shuffle = function() {
+        return this.each(function(){
+            var items = $(this).children();
+            return (items.length)
+                ? $(this).html($.shuffle(items))
+                : this;
+        });
+    }
+
+    $.shuffle = function(arr) {
+        for(
+            var j, x, i = arr.length; i;
+            j = parseInt(Math.random() * i),
+                x = arr[--i], arr[i] = arr[j], arr[j] = x
+        );
+        return arr;
+    }
+})(jQuery);
+
+/*
+* Assigns any event handler to any element
+* @param	object	Element on which the event is added
+* @param	string	Name of event
+* @param	string	Function to trigger on event
+* @param	boolean	Capture the event and prevent
+*/
+function addEvent(elm, evType, fn, useCapture)
+{
+    if (elm.addEventListener) {
+        elm.addEventListener(evType, fn, useCapture);
+        return true;
+    } else if(elm.attachEvent) {
+        var r = elm.attachEvent('on' + evType, fn);
+        return r;
+    } else {
+        elm['on' + evType] = fn;
+    }
+}

+ 16 - 33
main/lp/scorm.class.php

@@ -63,13 +63,8 @@ class scorm extends learnpath
 
     /**
      * Possible SCO status: see CAM doc 2.3.2.5.1: passed, completed, browsed, failed, not attempted, incomplete.
-     */
-
-    /**
      * Prerequisites: see CAM doc 2.3.2.5.1 for pseudo-code.
-     */
-
-    /**
+     *
      * Parses an imsmanifest.xml file and puts everything into the $manifest array.
      *
      * @param	string	Path to the imsmanifest.xml file on the system.
@@ -289,7 +284,7 @@ class scorm extends learnpath
                                 '$iFrameHolder.html(iFrameTag);',
                             ];
                             $replace = [
-                                'iFrameTag = \'<a target ="_blank" href="'.$proxyPath.'?type=link&src=\'+ pageSrc + \'">Open website. <img src="'.api_get_path(WEB_CODE_PATH).'img/link-external.png"></a>\'; $iFrameHolder.html(iFrameTag); ',
+                                'iFrameTag = \'<a target ="_blank" href="'.$proxyPath.'?type=link&src=\'+ pageSrc + \'">Open website. <img width="16px" src="'.Display::returnIconPath('link-external.png').'"></a>\'; $iFrameHolder.html(iFrameTag); ',
                             ];
                             $content = str_replace($find, $replace, $content);
                             file_put_contents($framePath, $content);
@@ -340,15 +335,15 @@ class scorm extends learnpath
         $courseInfo = api_get_course_info($courseCode);
         $courseId = $courseInfo['real_id'];
 
-        $userId = intval($userId);
+        $userId = (int) $userId;
         if (empty($userId)) {
             $userId = api_get_user_id();
         }
         // Get table names.
         $new_lp = Database::get_course_table(TABLE_LP_MAIN);
         $new_lp_item = Database::get_course_table(TABLE_LP_ITEM);
-        $userMaxScore = intval($userMaxScore);
-        $sessionId = empty($sessionId) ? api_get_session_id() : intval($sessionId);
+        $userMaxScore = (int) $userMaxScore;
+        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
         foreach ($this->organizations as $id => $dummy) {
             $oOrganization = &$this->organizations[$id];
             // Prepare and execute insert queries:
@@ -472,13 +467,12 @@ class scorm extends learnpath
                 }
                 $title = Database::escape_string($item['title']);
                 $title = api_utf8_decode($title);
+                $max_score = (int) $item['max_score'];
 
-                $max_score = intval($item['max_score']);
-
-                if ($max_score == 0 || is_null($max_score) || $max_score == '') {
+                if ($max_score === 0) {
                     // If max score is not set The use_max_score parameter
                     // is check in order to use 100 (chamilo style) or '' (strict scorm)
-                    $max_score = "NULL";
+                    $max_score = 'NULL';
                     if ($userMaxScore) {
                         $max_score = 100;
                     }
@@ -547,11 +541,12 @@ class scorm extends learnpath
                     $courseid = api_get_course_id();
                     $ic_slide->addCourseId($courseid);
                     $ic_slide->addToolId(TOOL_LEARNPATH);
+                    // TODO: Unify with other lp types.
                     $xapian_data = [
                         SE_COURSE_ID => $courseid,
                         SE_TOOL_ID => TOOL_LEARNPATH,
-                        SE_DATA => ['lp_id' => $lp_id, 'lp_item' => $previous, 'document_id' => ''], // TODO: Unify with other lp types.
-                        SE_USER => (int) api_get_user_id(),
+                        SE_DATA => ['lp_id' => $lp_id, 'lp_item' => $previous, 'document_id' => ''],
+                        SE_USER => api_get_user_id(),
                     ];
                     $ic_slide->xapian_data = serialize($xapian_data);
                     $di->addChunk($ic_slide);
@@ -607,7 +602,9 @@ class scorm extends learnpath
         $lpToCheck = null
     ) {
         if ($this->debug > 0) {
-            error_log('In scorm::import_package('.print_r($zip_file_info, true).',"'.$current_dir.'") method', 0);
+            error_log(
+                'In scorm::import_package('.print_r($zip_file_info, true).',"'.$current_dir.'") method'
+            );
         }
 
         $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
@@ -646,8 +643,6 @@ class scorm extends learnpath
         // Check the zip content (real size and file extension).
         $zipContentArray = $zipFile->listContent();
         $package_type = '';
-        $at_root = false;
-        $manifest = '';
         $manifest_list = [];
         // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
         $realFileSize = 0;
@@ -656,9 +651,7 @@ class scorm extends learnpath
                 $file = $thisContent['filename'];
                 $this->set_error_msg("File $file contains a PHP script");
             } elseif (stristr($thisContent['filename'], 'imsmanifest.xml')) {
-                //error_log('Found imsmanifest at '.$thisContent['filename'], 0);
                 if ($thisContent['filename'] == basename($thisContent['filename'])) {
-                    $at_root = true;
                 } else {
                     if ($this->debug > 2) {
                         error_log("New LP - subdir is now ".$this->subdir);
@@ -666,7 +659,6 @@ class scorm extends learnpath
                 }
                 $package_type = 'scorm';
                 $manifest_list[] = $thisContent['filename'];
-                $manifest = $thisContent['filename']; //just the relative directory inside scorm/
             }
             $realFileSize += $thisContent['size'];
         }
@@ -684,13 +676,10 @@ class scorm extends learnpath
 
         $this->subdir .= '/'.dirname($shortest_path); // Do not concatenate because already done above.
         $manifest = $shortest_path;
-        if ($this->debug > 1) {
-            error_log('New LP - Package type is now '.$package_type);
+        if ($this->debug) {
+            error_log("New LP - Package type is now: '$package_type'");
         }
         if ($package_type == '') {
-            if ($this->debug > 1) {
-                error_log('New LP - Package type is empty');
-            }
             Display::addFlash(
                 Display::return_message(get_lang('NotScormContent'))
             );
@@ -728,7 +717,6 @@ class scorm extends learnpath
         }
 
         /* Uncompressing phase */
-
         /*
             We need to process each individual file in the zip archive to
             - add it to the database
@@ -770,11 +758,6 @@ class scorm extends learnpath
                 }
                 while ($file = readdir($dir)) {
                     if ($file != '.' && $file != '..') {
-                        $filetype = 'file';
-                        if (is_dir($course_sys_dir.$new_dir.$file)) {
-                            $filetype = 'folder';
-                        }
-
                         // TODO: RENAMING FILES CAN BE VERY DANGEROUS SCORM-WISE, avoid that as much as possible!
                         //$safe_file = api_replace_dangerous_char($file, 'strict');
                         $find_str = ['\\', '.php', '.phtml'];

+ 32 - 38
main/lp/scorm_api.php

@@ -385,7 +385,7 @@ function Initialize() {
  * @return  string  All return values must be string (see SCORM)
  */
 function LMSGetValue(param) {
-    olms.G_LastError = G_NoError ;
+    olms.G_LastError = G_NoError;
     olms.G_LastErrorMessage = 'No error';
     var result='';
 
@@ -394,14 +394,13 @@ function LMSGetValue(param) {
          if (param == 'cmi.core.score.raw') {
              return '';
          }
-         olms.G_LastError 		= G_NotInitialized;
+         olms.G_LastError = G_NotInitialized;
          olms.G_LastErrorMessage = G_NotInitializedMessage;
          logit_scorm('LMSGetValue('+param+') on item id '+olms.lms_item_id+':<br />=> Error '+ G_NotInitialized + ' ' +G_NotInitializedMessage, 0);
          return '';
     }
 
-    //Chamilo does not support these SCO object properties
-
+    // Chamilo does not support these SCO object properties
     if (param == 'cmi.student_preference.text' ||
         param == 'cmi.student_preference.language' ||
         param == 'cmi.student_preference.speed' ||
@@ -410,8 +409,8 @@ function LMSGetValue(param) {
         param == 'cmi.student_data.time_limit_action' ||
         param == 'cmi.comments' ||
         param == 'cmi.comments_from_lms' ||
-/* The following properties were part of SCORM 1.0 or never implemented at all
- but seem to react badly to Captivate content producer when not defined */
+        /* The following properties were part of SCORM 1.0 or never implemented at all
+         but seem to react badly to Captivate content producer when not defined */
         param == 'cmi.student_demographics._children' ||
         param == 'cmi.student_demographics.city' ||
         param == 'cmi.student_demographics.class' ||
@@ -429,7 +428,7 @@ function LMSGetValue(param) {
         // the value is not supported
         olms.G_LastError = G_NotImplementedError  ;
         olms.G_LastErrorString = G_NotImplementedErrorMessage;
-        logit_scorm("LMSGetValue  ('"+param+"') Error '"+G_NotImplementedErrorMessage+"'",1);
+        logit_scorm("LMSGetValue ('"+param+"') Error '"+G_NotImplementedErrorMessage+"'",1);
         result = '';
         return result;
     }
@@ -449,7 +448,7 @@ function LMSGetValue(param) {
         } else {
             result='';
         }
-    } else if(param == 'cmi.core.exit'){
+    } else if(param == 'cmi.core.exit') {
         // ---- cmi.core.exit
         result='';
         olms.G_LastError = G_ElementIsWriteOnly;
@@ -458,8 +457,8 @@ function LMSGetValue(param) {
         olms.G_LastError = G_ElementIsWriteOnly;
     } else if(param == 'cmi.core.lesson_status'){
         // ---- cmi.core.lesson_status
-        if(olms.lesson_status != '') {
-            result=olms.lesson_status;
+        if (olms.lesson_status != '') {
+            result = olms.lesson_status;
         } else {
             //result='not attempted';
         }
@@ -637,18 +636,17 @@ function LMSSetValue(param, val) {
     olms.commit = true; //value has changed, need to re-commit
     olms.G_LastError = G_NoError ;
     olms.G_LastErrorMessage = 'No error';
-
     return_value = 'false';
 
-    if ( param == "cmi.core.score.raw" ) {
+    if (param == "cmi.core.score.raw") {
         olms.score= val;
         olms.updatable_vars_list['cmi.core.score.raw']=true;
         return_value='true';
-    } else if ( param == "cmi.core.score.max" ) {
+    } else if ( param == "cmi.core.score.max") {
         olms.max = val;
         olms.updatable_vars_list['cmi.core.score.max']=true;
         return_value='true';
-    } else if ( param == "cmi.core.score.min" ) {
+    } else if ( param == "cmi.core.score.min") {
         olms.min = val;
         olms.updatable_vars_list['cmi.core.score.min']=true;
         return_value='true';
@@ -681,9 +679,9 @@ function LMSSetValue(param, val) {
         success_status = val;
         olms.updatable_vars_list['cmi.success_status']=true;
         return_value='true'; //1.3
-    } else if ( param == "cmi.suspend_data" ) {
+    } else if ( param == "cmi.suspend_data") {
         olms.suspend_data = val;
-        olms.updatable_vars_list['cmi.suspend_data']=true;
+        olms.updatable_vars_list['cmi.suspend_data'] = true;
         return_value='true';
     } else if ( param == "cmi.core.exit" ) {
         olms.lms_item_core_exit = val;
@@ -797,11 +795,11 @@ function LMSSetValue(param, val) {
                         if(olms.item_objectives[obj_id]==null) {
                             olms.item_objectives[obj_id] = ['','','','',''];
                         }
-                        if( req_type == "id" ) {
-                                //olms.item_objectives[obj_id][0] = val.substring(51,57);
-                                olms.item_objectives[obj_id][0] = val;
-                                logit_scorm("Objective "+obj_id+"'s id updated",2);
-                                return_value = 'true';
+                        if (req_type == "id") {
+                            //olms.item_objectives[obj_id][0] = val.substring(51,57);
+                            olms.item_objectives[obj_id][0] = val;
+                            logit_scorm("Objective "+obj_id+"'s id updated",2);
+                            return_value = 'true';
                         } else if ( req_type == "score" ) {
                             if (myres[3] == '._children'){
                                 return_value = '';
@@ -819,18 +817,18 @@ function LMSSetValue(param, val) {
                                 olms.item_objectives[obj_id][4] = val;
                                 logit_scorm("Objective "+obj_id+"'s score min updated",2);
                                 return_value = 'true';
-                            } else{
+                            } else {
                                 return_value = '';
                                 olms.G_LastError = G_NotImplementedError;
                                 olms.G_LastErrorString = 'Not implemented yet';
                             }
                         } else if ( req_type == "status" ) {
-                                olms.item_objectives[obj_id][1] = val;
-                                logit_scorm("Objective "+obj_id+"'s status updated",2);
-                                return_value = 'true';
+                            olms.item_objectives[obj_id][1] = val;
+                            logit_scorm("Objective "+obj_id+"'s status updated",2);
+                            return_value = 'true';
                         } else {
-                                olms.G_LastError = G_NotImplementedError;
-                                olms.G_LastErrorString = 'Not implemented yet';
+                            olms.G_LastError = G_NotImplementedError;
+                            olms.G_LastErrorString = 'Not implemented yet';
                         }
                     }
                 }
@@ -863,13 +861,11 @@ function savedata(item_id) {
     logit_lms('function savedata(' + item_id + ')', 3);
 
     // Status is NOT modified here see the lp_ajax_save_item.php file
-
     if (olms.lesson_status != '') {
         olms.updatable_vars_list['cmi.core.lesson_status'] = true;
     }
 
     old_item_id = olms.info_lms_item[0];
-
     var item_to_save = olms.lms_item_id;
     logit_lms('item_to_save (original value): ' + item_to_save, 3);
 
@@ -982,8 +978,9 @@ function Finish(val) {
  * @return  string  Error code
  */
 function LMSGetLastError() {
-    logit_scorm('LMSGetLastError()',1);
-    return(olms.G_LastError.toString());
+    var error = olms.G_LastError.toString();
+    logit_scorm('LMSGetLastError() returned: ' + error, 1);
+    return error;
 }
 
 /**
@@ -1244,7 +1241,7 @@ function log_in_log(message, priority) {
  */
 function logit_lms(message, priority){
     if (scorm_logs) {
-        log_in_log("LMS: " + message + ' (# lms_item_id = '+olms.lms_item_id+')', priority);
+        log_in_log("LMS: " + message + ' (#lms_item_id = '+olms.lms_item_id+')', priority);
     }
     return false;
 }
@@ -1421,7 +1418,7 @@ function reinit_updatable_vars_list() {
             olms.updatable_vars_list[olms.scorm_variables[i]]=false;
         }
     }
-    olms.lesson_status='';
+    olms.lesson_status = 'not attempted';
 }
 
 /**
@@ -1894,9 +1891,7 @@ function xajax_save_item_scorm(
     params += '&userNavigatesAway='+userNavigatesAway;
     params += '&statusSignalReceived='+statusSignalReceived;
     var my_scorm_values = new Array();
-
     my_scorm_values = process_scorm_values();
-
     for (k=0; k < my_scorm_values.length; k++) {
         if (my_scorm_values[k]=='cmi.core.session_time') {
             params += '&t='+olms.session_time;
@@ -1928,7 +1923,6 @@ function xajax_save_item_scorm(
     if (is_interactions == 'true')  {
         interact_string = '';
         temp = '';
-
         for (i in olms.interactions) {
             interact_string += '&interact['+i+']=';
             interact_temp = '[';
@@ -2298,7 +2292,7 @@ function attach_glossary_into_scorm(type) {
                     var randLetter = String.fromCharCode(65 + Math.floor(Math.random() * 26));
                     var uniqid = randLetter + Date.now();
                     var openerId = uniqid +'_opener';
-                    var link = '<a id="'+openerId+'" class="generated" href="#">Open website <img src="<?php echo Display::returnIconPath('link-external.png'); ?>"/></a>';
+                    var link = '<a id="'+openerId+'" class="generated" href="#">Open website <img width="16px" src="<?php echo Display::returnIconPath('link-external.png'); ?>"/></a>';
                     var embed = $(this);
                     var height = embed.attr('height');
                     var width = embed.attr('width');
@@ -2363,7 +2357,7 @@ function attach_glossary_into_scorm(type) {
                         src = url+'&type=link&src='+src;
                         src = src.replace('https', 'http');
                         $(this).attr('href', src);
-                        var myAnchor = $('<a><img src="<?php echo Display::returnIconPath('link-external.png'); ?>"/></a>').attr("href", src).attr('target', '_blank').attr('class', 'generated');
+                        var myAnchor = $('<a><img width="16px" src="<?php echo Display::returnIconPath('link-external.png'); ?>"/></a>').attr("href", src).attr('target', '_blank').attr('class', 'generated');
                         $(this).after(myAnchor);
                         $(this).after('-');
                     }

+ 6 - 1
main/portfolio/add_category.php

@@ -4,7 +4,12 @@
 use Chamilo\CoreBundle\Entity\PortfolioCategory;
 
 $form = new FormValidator('add_category', 'post', "$baseUrl&action=add_category");
-$form->addText('title', get_lang('Title'));
+if (api_get_configuration_value('save_titles_as_html')) {
+    $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'NotebookStudent']);
+} else {
+    $form->addText('title', get_lang('Title'));
+    $form->applyFilter('title', 'trim');
+}
 $form->addHtmlEditor('description', get_lang('Description'), false, false, ['ToolbarSet' => 'Minimal']);
 $form->addButtonCreate(get_lang('Create'));
 

+ 6 - 1
main/portfolio/add_item.php

@@ -10,7 +10,12 @@ $categories = $em
     ]);
 
 $form = new FormValidator('add_portfolio', 'post', $baseUrl.'action=add_item');
-$form->addText('title', get_lang('Title'));
+if (api_get_configuration_value('save_titles_as_html')) {
+    $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'NotebookStudent']);
+} else {
+    $form->addText('title', get_lang('Title'));
+    $form->applyFilter('title', 'trim');
+}
 $form->addHtmlEditor('content', get_lang('Content'), true, false, ['ToolbarSet' => 'NotebookStudent']);
 $form->addSelectFromCollection('category', get_lang('Category'), $categories, [], true);
 $form->addButtonCreate(get_lang('Create'));

+ 6 - 1
main/portfolio/edit_category.php

@@ -2,7 +2,12 @@
 /* For licensing terms, see /license.txt */
 
 $form = new FormValidator('edit_category', 'post', $baseUrl."action=edit_category&id={$category->getId()}");
-$form->addText('title', get_lang('Title'));
+if (api_get_configuration_value('save_titles_as_html')) {
+    $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'NotebookStudent']);
+} else {
+    $form->addText('title', get_lang('Title'));
+    $form->applyFilter('title', 'trim');
+}
 $form->addHtmlEditor('description', get_lang('Description'), false, false, ['ToolbarSet' => 'Minimal']);
 $form->addButtonUpdate(get_lang('Update'));
 $form->setDefaults([

+ 7 - 2
main/portfolio/edit_item.php

@@ -8,7 +8,12 @@ $categories = $em
     ]);
 
 $form = new FormValidator('edit_portfolio', 'post', $baseUrl."action=edit_item&id={$item->getId()}");
-$form->addText('title', get_lang('Title'));
+if (api_get_configuration_value('save_titles_as_html')) {
+    $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'NotebookStudent']);
+} else {
+    $form->addText('title', get_lang('Title'));
+    $form->applyFilter('title', 'trim');
+}
 $form->addHtmlEditor('content', get_lang('Content'), true, false, ['ToolbarSet' => 'NotebookStudent']);
 $form->addSelectFromCollection('category', get_lang('Category'), $categories, [], true, '__toString');
 $form->addButtonUpdate(get_lang('Update'));
@@ -34,7 +39,7 @@ if ($form->validate()) {
     $em->flush();
 
     Display::addFlash(
-        Display::return_message(get_lang('Updated'), 'success')
+        Display::return_message(get_lang('ItemUpdated'), 'success')
     );
 
     header("Location: $baseUrl");

+ 2 - 2
main/portfolio/index.php

@@ -133,7 +133,7 @@ switch ($action) {
         $em->flush();
 
         Display::addFlash(
-            Display::return_message(get_lang('PortfolioItemDeleted'), 'success')
+            Display::return_message(get_lang('CategoryDeleted'), 'success')
         );
 
         header("Location: $baseUrl");
@@ -201,7 +201,7 @@ switch ($action) {
         $em->flush();
 
         Display::addFlash(
-            Display::return_message(get_lang('PortfolioItemDeleted'), 'success')
+            Display::return_message(get_lang('ItemDeleted'), 'success')
         );
 
         header("Location: $baseUrl");

+ 53 - 44
main/session/about.php

@@ -1,12 +1,13 @@
 <?php
 /* For licensing terms, see /license.txt */
 
+use Chamilo\CoreBundle\Entity\Course;
 use Chamilo\CoreBundle\Entity\ExtraField;
+use Chamilo\CoreBundle\Repository\SequenceRepository;
 use Chamilo\CoreBundle\Entity\SequenceResource;
+use Chamilo\CoreBundle\Entity\Session;
 use Chamilo\CourseBundle\Entity\CCourseDescription;
-use Chamilo\UserBundle\Repository\UserRepository;
 use Chamilo\UserBundle\Entity\User;
-use ChamiloSession as Session;
 
 /**
  * Session about page
@@ -25,16 +26,19 @@ $sessionId = isset($_GET['session_id']) ? (int) $_GET['session_id'] : 0;
 
 $em = Database::getManager();
 
+/** @var Session $session */
 $session = $em->find('ChamiloCoreBundle:Session', $sessionId);
 
 if (!$session) {
     api_not_allowed(true);
 }
+$htmlHeadXtra[] = api_get_asset('readmore-js/readmore.js');
 $courses = [];
 $sessionCourses = $em->getRepository('ChamiloCoreBundle:Session')->getCoursesOrderedByPosition($session);
 $fieldsRepo = $em->getRepository('ChamiloCoreBundle:ExtraField');
 $fieldTagsRepo = $em->getRepository('ChamiloCoreBundle:ExtraFieldRelTag');
 $userRepo = UserManager::getRepository();
+/** @var SequenceRepository $sequenceResourceRepo */
 $sequenceResourceRepo = $em->getRepository('ChamiloCoreBundle:SequenceResource');
 
 $tagField = $fieldsRepo->findOneBy([
@@ -46,6 +50,7 @@ $courseValues = new ExtraFieldValue('course');
 $userValues = new ExtraFieldValue('user');
 $sessionValues = new ExtraFieldValue('session');
 
+/** @var Course $sessionCourse */
 foreach ($sessionCourses as $sessionCourse) {
     $courseTags = [];
 
@@ -75,46 +80,48 @@ foreach ($sessionCourses as $sessionCourse) {
         $coachesData[] = $coachData;
     }
 
-    $courseDescriptionTools = $em->getRepository('ChamiloCourseBundle:CCourseDescription')
-        ->findBy(
-            [
-                'cId' => $sessionCourse->getId(),
-                'sessionId' => 0,
-            ],
-            [
-                'id' => 'DESC',
-                'descriptionType' => 'ASC',
-            ]
-        );
-
-    $courseDescription = $courseObjectives = $courseTopics = $courseMethodology = $courseMaterial = $courseResources = $courseAssessment = '';
+    $cd = new CourseDescription();
+    $cd->set_course_id($sessionCourse->getId());
+    $cd->set_session_id($session->getId());
+    $descriptionsData = $cd->get_description_data();
+
+    $courseDescription = [];
+    $courseObjectives = [];
+    $courseTopics = [];
+    $courseMethodology = [];
+    $courseMaterial = [];
+    $courseResources = [];
+    $courseAssessment = [];
     $courseCustom = [];
-    foreach ($courseDescriptionTools as $descriptionTool) {
-        switch ($descriptionTool->getDescriptionType()) {
-            case CCourseDescription::TYPE_DESCRIPTION:
-                $courseDescription = $descriptionTool;
-                break;
-            case CCourseDescription::TYPE_OBJECTIVES:
-                $courseObjectives = $descriptionTool;
-                break;
-            case CCourseDescription::TYPE_TOPICS:
-                $courseTopics = $descriptionTool;
-                break;
-            case CCourseDescription::TYPE_METHODOLOGY:
-                $courseMethodology = $descriptionTool;
-                break;
-            case CCourseDescription::TYPE_COURSE_MATERIAL:
-                $courseMaterial = $descriptionTool;
-                break;
-            case CCourseDescription::TYPE_RESOURCES:
-                $courseResources = $descriptionTool;
-                break;
-            case CCourseDescription::TYPE_ASSESSMENT:
-                $courseAssessment = $descriptionTool;
-                break;
-            case CCourseDescription::TYPE_CUSTOM:
-                $courseCustom[] = $descriptionTool;
-                break;
+
+    if (!empty($descriptionsData['descriptions'])) {
+        foreach ($descriptionsData['descriptions'] as $descriptionInfo) {
+            switch ($descriptionInfo['description_type']) {
+                case CCourseDescription::TYPE_DESCRIPTION:
+                    $courseDescription[] = $descriptionInfo;
+                    break;
+                case CCourseDescription::TYPE_OBJECTIVES:
+                    $courseObjectives[] = $descriptionInfo;
+                    break;
+                case CCourseDescription::TYPE_TOPICS:
+                    $courseTopics[] = $descriptionInfo;
+                    break;
+                case CCourseDescription::TYPE_METHODOLOGY:
+                    $courseMethodology[] = $descriptionInfo;
+                    break;
+                case CCourseDescription::TYPE_COURSE_MATERIAL:
+                    $courseMaterial[] = $descriptionInfo;
+                    break;
+                case CCourseDescription::TYPE_RESOURCES:
+                    $courseResources[] = $descriptionInfo;
+                    break;
+                case CCourseDescription::TYPE_ASSESSMENT:
+                    $courseAssessment[] = $descriptionInfo;
+                    break;
+                case CCourseDescription::TYPE_CUSTOM:
+                    $courseCustom[] = $descriptionInfo;
+                    break;
+            }
         }
     }
 
@@ -206,8 +213,8 @@ if ($checker) {
         BuyCoursesPlugin::PRODUCT_TYPE_SESSION
     );
     if ($sessionIsPremium) {
-        Session::write('SessionIsPremium', true);
-        Session::write('sessionId', $sessionId);
+        ChamiloSession::write('SessionIsPremium', true);
+        ChamiloSession::write('sessionId', $sessionId);
     }
 }
 
@@ -216,7 +223,9 @@ $redirectToSession = $redirectToSession ? '?s='.$sessionId : false;
 
 $coursesInThisSession = SessionManager::get_course_list_by_session_id($sessionId);
 $coursesCount = count($coursesInThisSession);
-$redirectToSession = $coursesCount == 1 && $redirectToSession ? $redirectToSession.'&cr='.array_values($coursesInThisSession)[0]['directory'] : $redirectToSession;
+$redirectToSession = $coursesCount == 1 && $redirectToSession
+    ? ($redirectToSession.'&cr='.array_values($coursesInThisSession)[0]['directory'])
+    : $redirectToSession;
 
 $template->assign('redirect_to_session', $redirectToSession);
 $template->assign('courses', $courses);

+ 3 - 3
main/social/personal_data.php

@@ -100,7 +100,7 @@ switch ($action) {
             UserManager::createDataPrivacyExtraFields();
 
             // Remove delete agreement if it was sent:
-            UserManager::update_extra_field_value(
+            /*UserManager::update_extra_field_value(
                 $userId,
                 'request_for_legal_agreement_consent_removal',
                 ''
@@ -110,7 +110,7 @@ switch ($action) {
                 $userId,
                 'request_for_legal_agreement_consent_removal_justification',
                 ''
-            );
+            );*/
 
             UserManager::update_extra_field_value(
                 $userId,
@@ -181,7 +181,7 @@ switch ($action) {
                 $userInfo
             );
 
-            $url = api_get_path(WEB_CODE_PATH).'admin/';
+            $url = api_get_path(WEB_CODE_PATH).'admin/user_list_consent.php';
             $link = Display::url($url, $url);
             $subject = get_lang('RequestForLegalConsentRemoval');
             $content = sprintf(

+ 27 - 6
main/survey/create_new_survey.php

@@ -18,6 +18,8 @@ require_once __DIR__.'/../inc/global.inc.php';
 
 $this_section = SECTION_COURSES;
 
+$allowSurveyAvailabilityDatetime = api_get_configuration_value('allow_survey_availability_datetime');
+
 // Database table definitions
 $table_survey = Database::get_course_table(TABLE_SURVEY);
 $table_user = Database::get_main_table(TABLE_MAIN_USER);
@@ -72,6 +74,13 @@ if ($_GET['action'] == 'edit' && isset($survey_id) && is_numeric($survey_id)) {
     $defaults['survey_id'] = $survey_id;
     $defaults['anonymous'] = $survey_data['anonymous'];
 
+    if ($allowSurveyAvailabilityDatetime) {
+        $defaults['avail_from'] = api_get_local_time($defaults['avail_from'], null, 'UTC');
+        $defaults['avail_till'] = api_get_local_time($defaults['avail_till'], null, 'UTC');
+        $defaults['start_date'] = $defaults['avail_from'];
+        $defaults['end_date'] = $defaults['avail_till'];
+    }
+
     $link_info = GradebookUtils::isResourceInCourseGradebook(
         $course_id,
         $gradebook_link_type,
@@ -93,9 +102,15 @@ if ($_GET['action'] == 'edit' && isset($survey_id) && is_numeric($survey_id)) {
     }
 } else {
     $defaults['survey_language'] = $_course['language'];
-    $defaults['start_date'] = date('Y-m-d', api_strtotime(api_get_local_time()));
+    $defaults['start_date'] = date(
+        $allowSurveyAvailabilityDatetime ? 'Y-m-d 00:00:00' : 'Y-m-d',
+        api_strtotime(api_get_local_time())
+    );
     $startdateandxdays = time() + 864000; // today + 10 days
-    $defaults['end_date'] = date('Y-m-d', $startdateandxdays);
+    $defaults['end_date'] = date(
+        $allowSurveyAvailabilityDatetime ? 'Y-m-d 23:59:59' : 'Y-m-d',
+        $startdateandxdays
+    );
     //$defaults['survey_share']['survey_share'] = 0;
     //$form_share_value = 1;
     $defaults['anonymous'] = 0;
@@ -149,8 +164,14 @@ $form->addElement(
 
 // Pass the language of the survey in the form
 $form->addElement('hidden', 'survey_language');
-$form->addElement('date_picker', 'start_date', get_lang('StartDate'));
-$form->addElement('date_picker', 'end_date', get_lang('EndDate'));
+
+if ($allowSurveyAvailabilityDatetime) {
+    $form->addDateTimePicker('start_date', get_lang('StartDate'));
+    $form->addDateTimePicker('end_date', get_lang('EndDate'));
+} else {
+    $form->addElement('date_picker', 'start_date', get_lang('StartDate'));
+    $form->addElement('date_picker', 'end_date', get_lang('EndDate'));
+}
 
 $form->addElement('checkbox', 'anonymous', null, get_lang('Anonymous'));
 $visibleResults = [
@@ -298,8 +319,8 @@ if ($_GET['action'] == 'add') {
     $form->addRule('survey_code', '', 'maxlength', 20);
 }
 $form->addRule('survey_title', get_lang('ThisFieldIsRequired'), 'required');
-$form->addRule('start_date', get_lang('InvalidDate'), 'date');
-$form->addRule('end_date', get_lang('InvalidDate'), 'date');
+$form->addRule('start_date', get_lang('InvalidDate'), $allowSurveyAvailabilityDatetime ? 'datetime' : 'date');
+$form->addRule('end_date', get_lang('InvalidDate'), $allowSurveyAvailabilityDatetime ? 'datetime' : 'date');
 $form->addRule(
     ['start_date', 'end_date'],
     get_lang('StartDateShouldBeBeforeEndDate'),

+ 13 - 4
main/survey/survey.lib.php

@@ -218,6 +218,7 @@ class SurveyManager
      */
     public static function store_survey($values)
     {
+        $allowSurveyAvailabilityDatetime = api_get_configuration_value('allow_survey_availability_datetime');
         $_user = api_get_user_info();
         $course_id = api_get_course_int_id();
         $session_id = api_get_session_id();
@@ -338,8 +339,12 @@ class SurveyManager
                 'subtitle' => $values['survey_subtitle'],
                 'author' => $_user['user_id'],
                 'lang' => $values['survey_language'],
-                'avail_from' => $values['start_date'],
-                'avail_till' => $values['end_date'],
+                'avail_from' => $allowSurveyAvailabilityDatetime
+                    ? api_get_utc_datetime($values['start_date'].':00')
+                    : $values['start_date'],
+                'avail_till' => $allowSurveyAvailabilityDatetime
+                    ? api_get_utc_datetime($values['end_date'].':59')
+                    : $values['end_date'],
                 'is_shared' => $shared_survey_id,
                 'template' => 'template',
                 'intro' => $values['survey_introduction'],
@@ -444,8 +449,12 @@ class SurveyManager
                 'subtitle' => $values['survey_subtitle'],
                 'author' => $_user['user_id'],
                 'lang' => $values['survey_language'],
-                'avail_from' => $values['start_date'],
-                'avail_till' => $values['end_date'],
+                'avail_from' => $allowSurveyAvailabilityDatetime
+                    ? api_get_utc_datetime($values['start_date'].':00')
+                    : $values['start_date'],
+                'avail_till' => $allowSurveyAvailabilityDatetime
+                    ? api_get_utc_datetime($values['end_date'].':59')
+                    : $values['end_date'],
                 'is_shared' => $shared_survey_id,
                 'template' => 'template',
                 'intro' => $values['survey_introduction'],

+ 4 - 12
main/tracking/courseLog.php

@@ -16,24 +16,16 @@ $from = isset($_GET['from']) ? $_GET['from'] : null;
 
 // Starting the output buffering when we are exporting the information.
 $export_csv = isset($_GET['export']) && $_GET['export'] == 'csv' ? true : false;
-$session_id = isset($_REQUEST['id_session']) ? intval($_REQUEST['id_session']) : 0;
+$session_id = isset($_REQUEST['id_session']) ? (int) $_REQUEST['id_session'] : 0;
 
+$this_section = SECTION_COURSES;
 if ($from == 'myspace') {
     $from_myspace = true;
-    $this_section = "session_my_space";
-} else {
-    $this_section = SECTION_COURSES;
+    $this_section = 'session_my_space';
 }
 
 // Access restrictions.
-$is_allowedToTrack =
-    api_is_platform_admin() ||
-    SessionManager::user_is_general_coach(api_get_user_id(), $session_id) ||
-    api_is_allowed_to_create_course() ||
-    api_is_session_admin() ||
-    api_is_drh() ||
-    api_is_course_tutor() ||
-    api_is_course_admin();
+$is_allowedToTrack = Tracking::isAllowToTrack($session_id);
 
 if (!$is_allowedToTrack) {
     api_not_allowed(true);

+ 5 - 7
main/tracking/course_log_events.php

@@ -7,9 +7,12 @@
 require_once __DIR__.'/../inc/global.inc.php';
 $this_section = SECTION_COURSES;
 
+$course_id = api_get_course_int_id();
+$course_code = api_get_course_id();
+$sessionId = api_get_session_id();
+
 // Access restrictions.
-$is_allowedToTrack = api_is_platform_admin() || api_is_allowed_to_create_course() ||
-    api_is_session_admin() || api_is_drh() || api_is_course_tutor();
+$is_allowedToTrack = Tracking::isAllowToTrack($sessionId);
 
 if (!$is_allowedToTrack) {
     api_not_allowed(true);
@@ -18,11 +21,6 @@ if (!$is_allowedToTrack) {
 // Starting the output buffering when we are exporting the information.
 $export_csv = isset($_GET['export']) && $_GET['export'] == 'csv' ? true : false;
 $exportXls = isset($_GET['export']) && $_GET['export'] == 'xls' ? true : false;
-
-$course_id = api_get_course_int_id();
-$course_code = api_get_course_id();
-$sessionId = api_get_session_id();
-
 $keyword = isset($_GET['keyword']) ? Security::remove_XSS($_GET['keyword']) : '';
 
 // jqgrid will use this URL to do the selects

+ 6 - 8
main/tracking/course_log_groups.php

@@ -6,26 +6,24 @@ require_once __DIR__.'/../inc/global.inc.php';
 $from_myspace = false;
 $from = isset($_GET['from']) ? $_GET['from'] : null;
 
+$course_id = api_get_course_int_id();
+$course_code = api_get_course_id();
+$sessionId = api_get_session_id();
+
+$this_section = SECTION_COURSES;
 if ($from == 'myspace') {
     $from_myspace = true;
     $this_section = "session_my_space";
-} else {
-    $this_section = SECTION_COURSES;
 }
 
 // Access restrictions.
-$is_allowedToTrack = api_is_platform_admin() || api_is_allowed_to_create_course() ||
-    api_is_session_admin() || api_is_drh() || api_is_course_tutor();
+$is_allowedToTrack = Tracking::isAllowToTrack($sessionId);
 
 if (!$is_allowedToTrack) {
     api_not_allowed(true);
     exit;
 }
 
-$course_id = api_get_course_int_id();
-$course_code = api_get_course_id();
-$sessionId = api_get_session_id();
-
 // jqgrid will use this URL to do the selects
 $url = api_get_path(WEB_AJAX_PATH).'model.ajax.php?a=get_group_reporting&course_id='.$course_id.'&session_id='.$sessionId;
 

+ 7 - 10
main/tracking/course_log_resources.php

@@ -10,26 +10,23 @@ $current_course_tool = TOOL_TRACKING;
 $from_myspace = false;
 $from = isset($_GET['from']) ? $_GET['from'] : null;
 
+$export_csv = isset($_GET['export']) && $_GET['export'] == 'csv' ? true : false;
+$exportXls = isset($_GET['export']) && $_GET['export'] == 'xls' ? true : false;
+$session_id = intval($_REQUEST['id_session']);
+
+$this_section = SECTION_COURSES;
 if ($from == 'myspace') {
     $from_myspace = true;
-    $this_section = "session_my_space";
-} else {
-    $this_section = SECTION_COURSES;
+    $this_section = 'session_my_space';
 }
 
 // Access restrictions.
-$is_allowedToTrack = api_is_platform_admin() || api_is_allowed_to_create_course() ||
-    api_is_session_admin() || api_is_drh() || api_is_course_tutor();
+$is_allowedToTrack = Tracking::isAllowToTrack($session_id);
 
 if (!$is_allowedToTrack) {
     api_not_allowed(true);
 }
 
-// Starting the output buffering when we are exporting the information.
-$export_csv = isset($_GET['export']) && $_GET['export'] == 'csv' ? true : false;
-$exportXls = isset($_GET['export']) && $_GET['export'] == 'xls' ? true : false;
-$session_id = intval($_REQUEST['id_session']);
-
 if ($export_csv || $exportXls) {
     $csvData = TrackingCourseLog::get_item_resources_data(0, 0, '', '');
     array_walk(

+ 7 - 12
main/tracking/course_log_tools.php

@@ -14,17 +14,14 @@ $groupId = isset($_REQUEST['gidReq']) ? intval($_REQUEST['gidReq']) : 0;
 $from_myspace = false;
 $from = isset($_GET['from']) ? $_GET['from'] : null;
 
+$this_section = SECTION_COURSES;
 if ($from == 'myspace') {
     $from_myspace = true;
-    $this_section = "session_my_space";
-} else {
-    $this_section = SECTION_COURSES;
+    $this_section = 'session_my_space';
 }
 
 // Access restrictions.
-$is_allowedToTrack = api_is_platform_admin(true, true) ||
-    api_is_allowed_to_create_course() ||
-    api_is_course_tutor();
+$is_allowedToTrack = Tracking::isAllowToTrack($session_id);
 
 if (!$is_allowedToTrack) {
     api_not_allowed(true);
@@ -71,7 +68,7 @@ if (isset($_GET['origin']) && $_GET['origin'] == 'resume_session') {
     ];
 }
 
-$view = (isset($_REQUEST['view']) ? $_REQUEST['view'] : '');
+$view = isset($_REQUEST['view']) ? $_REQUEST['view'] : '';
 $nameTools = get_lang('Tracking');
 
 // Display the header.
@@ -98,16 +95,13 @@ if (empty($session_id)) {
     );
 }
 $nbStudents = count($a_students);
-
 $student_ids = array_keys($a_students);
 $studentCount = count($student_ids);
 
 /* MAIN CODE */
 
 echo '<div class="actions">';
-
 echo TrackingCourseLog::actionsLeft('courses', api_get_session_id());
-
 echo '<span style="float:right; padding-top:0px;">';
 echo '<a href="javascript: void(0);" onclick="javascript: window.print();">'.
     Display::return_icon('printer.png', get_lang('Print'), '', ICON_SIZE_MEDIUM).'</a>';
@@ -152,11 +146,12 @@ if ($lpReporting) {
                     $session_id
                 );
             }
+
+            $lp_avg_progress = null;
             if ($studentCount > 0) {
                 $lp_avg_progress = $lp_avg_progress / $studentCount;
-            } else {
-                $lp_avg_progress = null;
             }
+
             // Separated presentation logic.
             if (is_null($lp_avg_progress)) {
                 $lp_avg_progress = '0%';

+ 3 - 4
main/tracking/course_session_report.php

@@ -9,9 +9,9 @@
 $cidReset = true;
 require_once __DIR__.'/../inc/global.inc.php';
 
-$this_section = "session_my_space";
-
-$is_allowedToTrack = $is_courseAdmin || $is_platformAdmin || $is_session_general_coach || $is_sessionAdmin;
+$session_id = isset($_GET['session_id']) ? intval($_GET['session_id']) : null;
+$this_section = 'session_my_space';
+$is_allowedToTrack = Tracking::isAllowToTrack($session_id);
 
 if (!$is_allowedToTrack) {
     api_not_allowed(true);
@@ -28,7 +28,6 @@ if (api_is_platform_admin()) {
 }
 $global = true;
 
-$session_id = isset($_GET['session_id']) ? intval($_GET['session_id']) : null;
 if (empty($session_id)) {
     $session_id = 1;
 }

+ 3 - 6
main/tracking/lp_results_by_user.php

@@ -12,12 +12,10 @@ require_once __DIR__.'/../inc/global.inc.php';
 
 $this_section = SECTION_TRACKING;
 
-$is_allowedToTrack = $is_courseAdmin || $is_platformAdmin || $is_session_general_coach || $is_sessionAdmin;
+$is_allowedToTrack = Tracking::isAllowToTrack(api_get_session_id());
 
 if (!$is_allowedToTrack) {
-    Display :: display_header(null);
-    api_not_allowed();
-    Display :: display_footer();
+    api_not_allowed(true);
 }
 
 $export_to_csv = false;
@@ -25,10 +23,9 @@ if (isset($_GET['export'])) {
     $export_to_csv = true;
 }
 
+$global = false;
 if (api_is_platform_admin()) {
     $global = true;
-} else {
-    $global = false;
 }
 
 if ($global) {

+ 1 - 1
main/tracking/question_course_report.php

@@ -11,7 +11,7 @@ require_once __DIR__.'/../inc/global.inc.php';
 
 $this_section = 'session_my_space';
 
-$allow = api_is_platform_admin(true) || api_is_course_admin() || $is_session_general_coach;
+$allow = Tracking::isAllowToTrack(api_get_session_id());
 
 if (!$allow) {
     api_not_allowed(true);

+ 13 - 14
main/tracking/total_time.php

@@ -18,22 +18,14 @@ $from = isset($_GET['from']) ? $_GET['from'] : null;
 $export_csv = isset($_GET['export']) && $_GET['export'] == 'csv' ? true : false;
 $session_id = isset($_REQUEST['id_session']) ? intval($_REQUEST['id_session']) : 0;
 
+$this_section = SECTION_COURSES;
 if ($from == 'myspace') {
     $from_myspace = true;
-    $this_section = "session_my_space";
-} else {
-    $this_section = SECTION_COURSES;
+    $this_section = 'session_my_space';
 }
 
 // Access restrictions.
-$is_allowedToTrack =
-    api_is_platform_admin() ||
-    SessionManager::user_is_general_coach(api_get_user_id(), $session_id) ||
-    api_is_allowed_to_create_course() ||
-    api_is_session_admin() ||
-    api_is_drh() ||
-    api_is_course_tutor() ||
-    api_is_course_admin();
+$is_allowedToTrack = Tracking::isAllowToTrack($session_id);
 
 if (!$is_allowedToTrack) {
     api_not_allowed(true);
@@ -44,7 +36,8 @@ if (!$is_allowedToTrack) {
 if (api_is_drh()) {
     // Blocking course for drh
     if (api_drh_can_access_all_session_content()) {
-        // If the drh has been configured to be allowed to see all session content, give him access to the session courses
+        // If the drh has been configured to be allowed to see all session content,
+        // give him access to the session courses
         $coursesFromSession = SessionManager::getAllCoursesFollowedByUser(
             api_get_user_id(),
             null
@@ -320,11 +313,17 @@ if (count($a_students) > 0) {
     $headers['login'] = get_lang('Login');
 
     $table->set_header(4, get_lang('TrainingTime').'&nbsp;'.
-        Display::return_icon('info3.gif', get_lang('CourseTimeInfo'), ['align' => 'absmiddle', 'hspace' => '3px']), false, ['style' => 'width:110px;']);
+        Display::return_icon('info3.gif', get_lang('CourseTimeInfo'), ['align' => 'absmiddle', 'hspace' => '3px']),
+        false,
+        ['style' => 'width:110px;']
+    );
     $headers['training_time'] = get_lang('TrainingTime');
 
     $table->set_header(5, get_lang('TotalLPTime').'&nbsp;'.
-        Display::return_icon('info3.gif', get_lang('TotalLPTime'), ['align' => 'absmiddle', 'hspace' => '3px']), false, ['style' => 'width:110px;']);
+        Display::return_icon('info3.gif', get_lang('TotalLPTime'), ['align' => 'absmiddle', 'hspace' => '3px']),
+        false,
+        ['style' => 'width:110px;']
+    );
     $headers['total_time_lp'] = get_lang('TotalLPTime');
 
     $table->set_header(6, get_lang('FirstLoginInCourse'), false);

+ 2 - 109
main/work/work.lib.php

@@ -56,17 +56,7 @@ function displayWorkActionLinks($id, $action, $isTutor)
         }
     }
 
-    if (api_is_allowed_to_edit(null, true) && $origin != 'learnpath') {
-        if (empty($id)) {
-            // Options
-            $output .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=settings">';
-            $output .= Display::return_icon(
-                'settings.png',
-                get_lang('EditToolOptions'),
-                '',
-                ICON_SIZE_MEDIUM
-            ).'</a>';
-        }
+    if (api_is_allowed_to_edit(null, true) && $origin != 'learnpath' && $action == 'list') {
         $output .= '<a id="open-view-list" href="#">'.
             Display::return_icon(
                 'listwork.png',
@@ -84,45 +74,6 @@ function displayWorkActionLinks($id, $action, $isTutor)
     }
 }
 
-/**
- * Returns a form displaying all options for this tool.
- * These are
- * - make all files visible / invisible
- * - set the default visibility of uploaded files.
- *
- * @param $defaults
- *
- * @return string The HTML form
- */
-function settingsForm($defaults)
-{
-    $allowed = api_is_allowed_to_edit(null, true);
-    if (!$allowed) {
-        return;
-    }
-
-    $url = api_get_path(WEB_CODE_PATH).'work/work.php?'.api_get_cidreq().'&action=settings';
-    $form = new FormValidator('edit_settings', 'post', $url);
-    $form->addElement('hidden', 'changeProperties', 1);
-    $form->addElement('header', get_lang('EditToolOptions'));
-
-    $group = [
-        $form->createElement('radio', 'show_score', null, get_lang('NewVisible'), 0),
-        $form->createElement('radio', 'show_score', null, get_lang('NewUnvisible'), 1),
-    ];
-    $form->addGroup($group, '', get_lang('DefaultUpload'));
-
-    $group = [
-        $form->createElement('radio', 'student_delete_own_publication', null, get_lang('Yes'), 1),
-        $form->createElement('radio', 'student_delete_own_publication', null, get_lang('No'), 0),
-    ];
-    $form->addGroup($group, '', get_lang('StudentAllowedToDeleteOwnPublication'));
-    $form->addButtonSave(get_lang('Save'));
-    $form->setDefaults($defaults);
-
-    return $form->returnForm();
-}
-
 /**
  * @param string $path
  * @param int    $courseId
@@ -4721,64 +4672,6 @@ function getUploadDocumentType()
     ];
 }
 
-/**
- * @param array $courseInfo
- * @param bool  $showScore
- * @param bool  $studentDeleteOwnPublication
- *
- * @return bool
- */
-function updateSettings($courseInfo, $showScore, $studentDeleteOwnPublication)
-{
-    $showScore = (int) $showScore;
-    $courseId = api_get_course_int_id();
-    $main_course_table = Database::get_main_table(TABLE_MAIN_COURSE);
-    $table_course_setting = Database::get_course_table(TOOL_COURSE_SETTING);
-
-    if (empty($courseId)) {
-        return false;
-    }
-
-    $query = "UPDATE $main_course_table
-              SET show_score = '$showScore'
-              WHERE id = $courseId";
-    Database::query($query);
-
-    /**
-     * Course data are cached in session so we need to update both the database
-     * and the session data.
-     */
-    $courseInfo['show_score'] = $showScore;
-    Session::write('_course', $courseInfo);
-
-    // changing the tool setting: is a student allowed to delete his/her own document
-    // counting the number of occurrences of this setting (if 0 => add, if 1 => update)
-    $query = "SELECT * FROM $table_course_setting
-              WHERE
-                c_id = $courseId AND
-                variable = 'student_delete_own_publication'";
-
-    $result = Database::query($query);
-    $number_of_setting = Database::num_rows($result);
-
-    if ($number_of_setting == 1) {
-        $query = "UPDATE $table_course_setting SET
-                  value = '".Database::escape_string($studentDeleteOwnPublication)."'
-                  WHERE variable = 'student_delete_own_publication' AND c_id = $courseId";
-        Database::query($query);
-    } else {
-        $params = [
-            'c_id' => $courseId,
-            'variable' => 'student_delete_own_publication',
-            'value' => $studentDeleteOwnPublication,
-            'category' => 'work',
-        ];
-        Database::insert($table_course_setting, $params);
-    }
-
-    return true;
-}
-
 /**
  * @param int   $item_id
  * @param array $course_info
@@ -5347,7 +5240,7 @@ function exportAllStudentWorkFromPublication(
     $expiresOn = null;
 
     if (!empty($assignment) && isset($assignment['expires_on'])) {
-        $content .= '<br /><strong>'.get_lang('ExpirationDate').'</strong>: '.api_get_local_time($assignment['expires_on']);
+        $content .= '<br /><strong>'.get_lang('PostedExpirationDate').'</strong>: '.api_get_local_time($assignment['expires_on']);
         $expiresOn = api_get_local_time($assignment['expires_on']);
     }
 

+ 2 - 30
main/work/work.php

@@ -90,7 +90,7 @@ if (!empty($groupId)) {
 } else {
     if ($origin != 'learnpath') {
         if (isset($_GET['id']) &&
-            !empty($_GET['id']) || $display_upload_form || $action == 'settings' || $action == 'create_dir'
+            !empty($_GET['id']) || $display_upload_form || $action == 'create_dir'
         ) {
             $interbreadcrumb[] = [
                 'url' => api_get_path(WEB_CODE_PATH).'work/work.php?'.api_get_cidreq(),
@@ -110,9 +110,7 @@ if (!empty($groupId)) {
         if ($action == 'upload_form') {
             $interbreadcrumb[] = ['url' => '#', 'name' => get_lang('UploadADocument')];
         }
-        if ($action == 'settings') {
-            $interbreadcrumb[] = ['url' => '#', 'name' => get_lang('EditToolOptions')];
-        }
+
         if ($action == 'create_dir') {
             $interbreadcrumb[] = ['url' => '#', 'name' => get_lang('CreateAssignment')];
         }
@@ -145,32 +143,6 @@ $content = null;
 
 // For teachers
 switch ($action) {
-    case 'settings':
-        //if posts
-        if ($is_allowed_to_edit && !empty($_POST['changeProperties'])) {
-            updateSettings(
-                $courseInfo,
-                $_POST['show_score'],
-                $_POST['student_delete_own_publication']
-            );
-            Display::addFlash(Display::return_message(get_lang('Saved'), 'success'));
-            header('Location: '.$currentUrl);
-            exit;
-        }
-
-        if (!$is_allowed_to_edit) {
-            api_not_allowed(true);
-        }
-
-        $studentDeleteOwnPublication = api_get_course_setting('student_delete_own_publication') == 1 ? 1 : 0;
-        /*	Display of tool options */
-        $content = settingsForm(
-            [
-                'show_score' => $courseInfo['show_score'],
-                'student_delete_own_publication' => $studentDeleteOwnPublication,
-            ]
-        );
-        break;
     case 'add':
     case 'create_dir':
         if (!($is_allowed_to_edit || $isTutor)) {

部分文件因文件數量過多而無法顯示