Minor - Merge from 1.11.x

pull/3063/head
jmontoyaa 8 years ago
parent 9a1819c331
commit d333ef6618
  1. 70
      main/exercise/exercise_show.php
  2. 17
      main/exercise/fill_blanks.class.php
  3. 1
      main/inc/ajax/course.ajax.php
  4. 3
      main/inc/ajax/exercise.ajax.php
  5. 26
      main/inc/lib/internationalization.lib.php
  6. 132
      main/inc/lib/tracking.lib.php
  7. 4
      main/inc/lib/webservices/Rest.php
  8. 327
      main/lp/learnpath.class.php
  9. 31
      main/lp/learnpathItem.class.php
  10. 3
      main/lp/lp_ajax_start_timer.php
  11. 10
      main/lp/lp_controller.php
  12. 10
      main/lp/lp_edit.php
  13. 1
      main/lp/lp_final_item.php
  14. 2
      main/lp/lp_impress.php
  15. 2
      main/lp/lp_list.php
  16. 4
      main/lp/scorm_api.php
  17. 2
      main/user/user.php

@ -234,44 +234,42 @@ if (!empty($track_exercise_info)) {
// if the results_disabled of the Quiz is 1 when block the script
$result_disabled = $track_exercise_info['results_disabled'];
if (true) {
if ($result_disabled == RESULT_DISABLE_NO_SCORE_AND_EXPECTED_ANSWERS) {
$show_results = false;
} elseif ($result_disabled == RESULT_DISABLE_SHOW_SCORE_ONLY) {
$show_results = false;
$show_only_total_score = true;
if ($origin != 'learnpath') {
if ($currentUserId == $student_id) {
echo Display::return_message(
get_lang('ThankYouForPassingTheTest'),
'warning',
false
);
}
}
} elseif ($result_disabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) {
$attempts = Event::getExerciseResultsByUser(
$currentUserId,
$objExercise->id,
api_get_course_int_id(),
api_get_session_id(),
$track_exercise_info['orig_lp_id'],
$track_exercise_info['orig_lp_item_id'],
'desc'
);
$numberAttempts = count($attempts);
if ($numberAttempts >= $track_exercise_info['max_attempt']) {
$show_results = true;
$show_only_total_score = true;
// Attempt reach max so show score/feedback now
$showTotalScoreAndUserChoicesInLastAttempt = true;
} else {
$show_results = true;
$show_only_total_score = true;
// Last attempt not reach don't show score/feedback
$showTotalScoreAndUserChoicesInLastAttempt = false;
if ($result_disabled == RESULT_DISABLE_NO_SCORE_AND_EXPECTED_ANSWERS) {
$show_results = false;
} elseif ($result_disabled == RESULT_DISABLE_SHOW_SCORE_ONLY) {
$show_results = false;
$show_only_total_score = true;
if ($origin != 'learnpath') {
if ($currentUserId == $student_id) {
echo Display::return_message(
get_lang('ThankYouForPassingTheTest'),
'warning',
false
);
}
}
} elseif ($result_disabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) {
$attempts = Event::getExerciseResultsByUser(
$currentUserId,
$objExercise->id,
api_get_course_int_id(),
api_get_session_id(),
$track_exercise_info['orig_lp_id'],
$track_exercise_info['orig_lp_item_id'],
'desc'
);
$numberAttempts = count($attempts);
if ($numberAttempts >= $track_exercise_info['max_attempt']) {
$show_results = true;
$show_only_total_score = true;
// Attempt reach max so show score/feedback now
$showTotalScoreAndUserChoicesInLastAttempt = true;
} else {
$show_results = true;
$show_only_total_score = true;
// Last attempt not reach don't show score/feedback
$showTotalScoreAndUserChoicesInLastAttempt = false;
}
}
} else {
echo Display::return_message(get_lang('CantViewResults'), 'warning');

@ -676,10 +676,8 @@ class FillBlanks extends Question
$correctAnswer = api_html_entity_decode($correctAnswer);
$studentAnswer = htmlspecialchars($studentAnswer);
$result = $studentAnswer == self::trimOption($correctAnswer);
break;
}
//var_dump($result);
return $result;
}
@ -953,10 +951,10 @@ class FillBlanks extends Question
{
$outRes = 0;
// for each student in group
foreach ($resultList as $userId => $tabValue) {
foreach ($resultList as $list) {
$found = false;
// for each bracket, if student has at least one answer ( choice > -2) then he pass the question
foreach ($tabValue as $i => $choice) {
foreach ($list as $choice) {
if ($choice > -2 && !$found) {
$outRes++;
$found = true;
@ -1177,10 +1175,9 @@ class FillBlanks extends Question
$listStudentAnswerInfo = self::getAnswerInfo($answer, true);
if ($resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) {
$resultsDisabled = true;
if ($showTotalScoreAndUserChoices) {
$resultsDisabled = false;
} else {
$resultsDisabled = true;
}
}
@ -1239,15 +1236,14 @@ class FillBlanks extends Question
$showTotalScoreAndUserChoices = false
) {
$hideExpectedAnswer = false;
if ($feedbackType == 0 && ($resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ONLY)) {
if ($feedbackType == 0 && $resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ONLY) {
$hideExpectedAnswer = true;
}
if ($resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) {
$hideExpectedAnswer = true;
if ($showTotalScoreAndUserChoices) {
$hideExpectedAnswer = false;
} else {
$hideExpectedAnswer = true;
}
}
@ -1273,7 +1269,7 @@ class FillBlanks extends Question
$correctAnswerHtml .= ")</span>";
break;
case self::FILL_THE_BLANK_SEVERAL_ANSWER:
$listCorrects = explode("||", $correct);
$listCorrects = explode('||', $correct);
$firstCorrect = $correct;
if (count($listCorrects) > 0) {
$firstCorrect = $listCorrects[0];
@ -1333,6 +1329,7 @@ class FillBlanks extends Question
* @param string $correct
* @param string $feedbackType
* @param bool $resultsDisabled
* @param bool $showTotalScoreAndUserChoices
*
* @return string
*/

@ -299,6 +299,7 @@ switch ($action) {
'cid' => api_get_course_int_id(),
'sid' => api_get_session_id(),
];
$result = (int) Event::courseLogout($logoutInfo);
echo $result;
break;

@ -7,12 +7,11 @@ use ChamiloSession as Session;
* Responses to AJAX calls.
*/
require_once __DIR__.'/../global.inc.php';
$debug = false;
api_protect_course_script(true);
$action = $_REQUEST['a'];
$course_id = api_get_course_int_id();
$debug = false;
if ($debug) {
error_log("-----------------");
error_log("$action ajax call");

@ -425,7 +425,6 @@ function api_get_utc_datetime(
$returnNullIfInvalidDate = false,
$returnObj = false
) {
$fromTimezone = api_get_timezone();
if (is_null($time) || empty($time) || $time === '0000-00-00 00:00:00') {
if ($returnNullIfInvalidDate) {
return null;
@ -443,8 +442,8 @@ function api_get_utc_datetime(
return gmdate('Y-m-d H:i:s', $time);
}
try {
$fromTimezone = api_get_timezone();
$date = new DateTime($time, new DateTimezone($fromTimezone));
$date->setTimezone(new DateTimeZone('UTC'));
if ($returnObj) {
@ -482,10 +481,6 @@ function api_get_local_time(
if (is_null($from_timezone)) {
$from_timezone = 'UTC';
}
// Determining the timezone to be converted to
if (is_null($to_timezone)) {
$to_timezone = api_get_timezone();
}
// If time is a timestamp, convert it to a string
if (is_null($time) || empty($time) || $time == '0000-00-00 00:00:00') {
@ -495,6 +490,7 @@ function api_get_local_time(
$from_timezone = 'UTC';
$time = gmdate('Y-m-d H:i:s');
}
if (is_numeric($time)) {
$time = intval($time);
if ($return_null_if_invalid_date) {
@ -512,6 +508,11 @@ function api_get_local_time(
}
try {
// Determining the timezone to be converted to
if (is_null($to_timezone)) {
$to_timezone = api_get_timezone();
}
$date = new DateTime($time, new DateTimezone($from_timezone));
$date->setTimezone(new DateTimeZone($to_timezone));
@ -1002,11 +1003,6 @@ function api_is_western_name_order($format = null, $language = null)
*/
function api_sort_by_first_name($language = null)
{
$userNameSortBy = api_get_setting('user_name_sort_by');
if (!empty($userNameSortBy) && in_array($userNameSortBy, ['firstname', 'lastname'])) {
return $userNameSortBy == 'firstname' ? true : false;
}
static $sort_by_first_name = [];
if (empty($language)) {
@ -2247,7 +2243,8 @@ function _api_get_character_map_name($encoding)
*/
/**
* A reverse function from php-core function strnatcmp(), performs string comparison in reverse natural (alpha-numerical) order.
* A reverse function from php-core function strnatcmp(),
* performs string comparison in reverse natural (alpha-numerical) order.
*
* @param string $string1 the first string
* @param string $string2 the second string
@ -2262,10 +2259,11 @@ function _api_strnatrcmp($string1, $string2)
/**
* Sets/Gets internal character encoding of the common string functions within the PHP mbstring extension.
*
* @param string $encoding (optional) When this parameter is given, the function sets the internal encoding
* @param string $encoding (optional) When this parameter is given, the function sets the internal encoding
*
* @return string When $encoding parameter is not given, the function returns the internal encoding.
* Note: This function is used in the global initialization script for setting the internal encoding to the platform's character set.
* Note: This function is used in the global initialization script for setting the
* internal encoding to the platform's character set.
*
* @see http://php.net/manual/en/function.mb-internal-encoding
*/

@ -2608,14 +2608,13 @@ class Tracking
}
$conditionToString = implode('AND', $conditions);
$sql = "
SELECT lp_id, view_count, progress
FROM $lpViewTable lp_view
WHERE
$conditionToString
$groupBy
ORDER BY view_count DESC
";
$sql = "SELECT lp_id, view_count, progress
FROM $lpViewTable lp_view
WHERE
$conditionToString
$groupBy
ORDER BY view_count DESC";
$result = Database::query($sql);
$progress = [];
@ -2720,7 +2719,6 @@ class Tracking
// Check the real number of LPs corresponding to the filter in the
// database (and if no list was given, get them all)
if (empty($session_id)) {
$sql = "SELECT DISTINCT(id), use_max_score
FROM $lp_table
@ -6731,7 +6729,8 @@ class Tracking
$row_ip = Database::fetch_row($res_ip);
if ($return_as_link) {
$ip = Display::url(
(empty($body_replace) ? $row_ip[1] : $body_replace), 'http://www.whatsmyip.org/ip-geo-location/?ip='.$row_ip[1],
(empty($body_replace) ? $row_ip[1] : $body_replace),
'http://www.whatsmyip.org/ip-geo-location/?ip='.$row_ip[1],
['title' => get_lang('TraceIP'), 'target' => '_blank']
);
} else {
@ -7823,4 +7822,117 @@ class TrackingCourseLog
return $users;
}
/**
* @param string $current
*/
public static function actionsLeft($current, $sessionId = 0)
{
$usersLink = Display::url(
Display::return_icon('user.png', get_lang('StudentsTracking'), [], ICON_SIZE_MEDIUM),
'courseLog.php?'.api_get_cidreq(true, false)
);
$groupsLink = Display::url(
Display::return_icon('group.png', get_lang('GroupReporting'), [], ICON_SIZE_MEDIUM),
'course_log_groups.php?'.api_get_cidreq()
);
$resourcesLink = Display::url(
Display::return_icon('tools.png', get_lang('ResourcesTracking'), [], ICON_SIZE_MEDIUM),
'course_log_resources.php?'.api_get_cidreq(true, false)
);
$courseLink = Display::url(
Display::return_icon('course.png', get_lang('CourseTracking'), [], ICON_SIZE_MEDIUM),
'course_log_tools.php?'.api_get_cidreq(true, false)
);
$examLink = Display::url(
Display::return_icon('quiz.png', get_lang('ExamTracking'), [], ICON_SIZE_MEDIUM),
api_get_path(WEB_CODE_PATH).'tracking/exams.php?'.api_get_cidreq()
);
$eventsLink = Display::url(
Display::return_icon('security.png', get_lang('EventsReport'), [], ICON_SIZE_MEDIUM),
api_get_path(WEB_CODE_PATH).'tracking/course_log_events.php?'.api_get_cidreq()
);
$attendanceLink = '';
if (!empty($sessionId)) {
$attendanceLink = Display::url(
Display::return_icon('attendance_list.png', get_lang('Logins'), '', ICON_SIZE_MEDIUM),
api_get_path(WEB_CODE_PATH).'attendance/index.php?'.api_get_cidreq().'&action=calendar_logins'
);
}
switch ($current) {
case 'users':
$usersLink = Display::url(
Display::return_icon(
'user_na.png',
get_lang('StudentsTracking'),
[],
ICON_SIZE_MEDIUM
),
'#'
);
break;
case 'groups':
$groupsLink = Display::url(
Display::return_icon('group_na.png', get_lang('GroupReporting'), [], ICON_SIZE_MEDIUM),
'#'
);
break;
case 'courses':
$courseLink = Display::url(
Display::return_icon('course_na.png', get_lang('CourseTracking'), [], ICON_SIZE_MEDIUM),
'#'
);
break;
case 'resources':
$resourcesLink = Display::url(
Display::return_icon(
'tools_na.png',
get_lang('ResourcesTracking'),
[],
ICON_SIZE_MEDIUM
),
'#'
);
break;
case 'exams':
$examLink = Display::url(
Display::return_icon('quiz_na.png', get_lang('ExamTracking'), [], ICON_SIZE_MEDIUM),
'#'
);
break;
case 'logs':
$eventsLink = Display::url(
Display::return_icon('security_na.png', get_lang('EventsReport'), [], ICON_SIZE_MEDIUM),
'#'
);
break;
case 'attendance':
if (!empty($sessionId)) {
$attendanceLink = Display::url(
Display::return_icon('attendance_list.png', get_lang('Logins'), '', ICON_SIZE_MEDIUM),
'#'
);
}
break;
}
$items = [
$usersLink,
$groupsLink,
$courseLink,
$resourcesLink,
$examLink,
$eventsLink,
$attendanceLink,
];
return implode('', $items).'&nbsp;';
}
}

@ -40,7 +40,9 @@ class Rest extends WebService
const GET_MESSAGE_USERS = 'message_users';
const SAVE_COURSE_NOTEBOOK = 'save_course_notebook';
const SAVE_FORUM_THREAD = 'save_forum_thread';
const SAVE_COURSE = 'save_course';
const SAVE_USER = 'save_user';
const SUBSCRIBE_USER_TO_COURSE = 'subscribe_user_to_course';
const EXTRAFIELD_GCM_ID = 'gcm_registration_id';
/**

@ -726,11 +726,11 @@ class learnpath
/**
* Static admin function allowing addition of a learnpath to a course.
*
* @param string Course code
* @param string Learnpath name
* @param string Learnpath description string, if provided
* @param string Type of learnpath (default = 'guess', others = 'dokeos', 'aicc',...)
* @param string Type of files origin (default = 'zip', others = 'dir','web_dir',...)
* @param string $courseCode
* @param string $name
* @param string $description
* @param string $learnpath
* @param string $origin
* @param string $zipname Zip file containing the learnpath or directory containing the learnpath
* @param string $publicated_on
* @param string $expired_on
@ -2128,8 +2128,8 @@ class learnpath
* has been written to test a zip file. If not a zip, the function will return the
* default return value: ''
*
* @param string the path to the file
* @param string the original name of the file
* @param string $file_path the path to the file
* @param string $file_name the original name of the file
*
* @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
*/
@ -2380,10 +2380,10 @@ class learnpath
* of its prerequisite is completed, considering the time availability and
* the LP visibility.
*
* @param int $lp_id
* @param int $student_id
* @param string Course code (optional)
* @param int $sessionId
* @param int $lp_id
* @param int $student_id
* @param null $courseCode
* @param int $sessionId
*
* @return bool
*/
@ -2774,7 +2774,7 @@ class learnpath
* Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
* same rule as the scorm_export() method.
*
* @param int Item ID
* @param int $item_id Item ID
*
* @return string Prerequisites string ready for the export as SCORM
*/
@ -2855,8 +2855,8 @@ class learnpath
/**
* Returns the XML DOM document's node.
*
* @param resource Reference to a list of objects to search for the given ITEM_*
* @param string The identifier to look for
* @param resource $children Reference to a list of objects to search for the given ITEM_*
* @param string $id The identifier to look for
*
* @return mixed The reference to the element found with that identifier. False if not found
*/
@ -2905,10 +2905,10 @@ class learnpath
* Return the number of interactions for the given learnpath Item View ID.
* This method can be used as static.
*
* @param int Item View ID
* @param int course id
* @param int $lp_iv_id Item View ID
* @param int $course_id course id
*
* @return int Number of interactions
* @return int
*/
public static function get_interactions_count_from_db($lp_iv_id, $course_id)
{
@ -2932,7 +2932,7 @@ class learnpath
* Return the interactions as an array for the given lp_iv_id.
* This method can be used as static.
*
* @param int Learnpath Item View ID
* @param int $lp_iv_id Learnpath Item View ID
*
* @return array
*
@ -2986,7 +2986,8 @@ class learnpath
* Return the number of objectives for the given learnpath Item View ID.
* This method can be used as static.
*
* @param int Item View ID
* @param int $lp_iv_id Item View ID
* @param int $course_id Course ID
*
* @return int Number of objectives
*/
@ -3116,7 +3117,7 @@ class learnpath
/**
* Gets the learning path type.
*
* @param bool Return the name? If false, return the ID. Default is false.
* @param bool $get_name Return the name? If false, return the ID. Default is false.
*
* @return mixed Type ID or name, depending on the parameter
*/
@ -3165,8 +3166,9 @@ class learnpath
* Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
* This method can be used as abstract and is recursive.
*
* @param int Learnpath ID
* @param int Parent ID of the items to look for
* @param int $lp Learnpath ID
* @param int $parent Parent ID of the items to look for
* @param int $course_id
*
* @return array Ordered list of item IDs (empty array on error)
*/
@ -3220,7 +3222,9 @@ class learnpath
/**
* Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
*
* @return string HTML TOC ready to display
* @param $tree
*
* @return array HTML TOC ready to display
*/
public function getParentToc($tree)
{
@ -3294,7 +3298,11 @@ class learnpath
/**
* Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
*
* @return string HTML TOC ready to display
* @param array $tree
* @param int $id
* @param bool $parent
*
* @return array HTML TOC ready to display
*/
public function getChildrenToc($tree, $id, $parent = true)
{
@ -3364,7 +3372,7 @@ class learnpath
*
* @param array $toc_list
*
* @return string HTML TOC ready to display
* @return array HTML TOC ready to display
*/
public function getListArrayToc($toc_list = [])
{
@ -3513,8 +3521,9 @@ class learnpath
/**
* Gets a link to the resource from the present location, depending on item ID.
*
* @param string $type Type of link expected
* @param int $item_id Learnpath item ID
* @param string $type Type of link expected
* @param int $item_id Learnpath item ID
* @param bool $provided_toc
*
* @return string $provided_toc Link to the lp_item resource
*/
@ -3842,7 +3851,7 @@ class learnpath
/**
* Gets the latest usable view or generate a new one.
*
* @param int Optional attempt number. If none given, takes the highest from the lp_view table
* @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
*
* @return int DB lp_view id
*/
@ -3961,10 +3970,10 @@ class learnpath
/**
* Moves an item up and down at its level.
*
* @param int Item to move up and down
* @param string Direction 'up' or 'down'
* @param int $id Item to move up and down
* @param string $direction Direction 'up' or 'down'
*
* @return int New display order, or false on error
* @return bool|int
*/
public function move_item($id, $direction)
{
@ -4125,7 +4134,7 @@ class learnpath
* Move a LP up (display_order).
*
* @param int $lp_id Learnpath ID
* @param int $categoryId
* @param int $categoryId Category ID
*
* @return bool
*/
@ -4186,7 +4195,7 @@ class learnpath
* Move a learnpath down (display_order).
*
* @param int $lp_id Learnpath ID
* @param int $categoryId
* @param int $categoryId Category ID
*
* @return bool
*/
@ -4279,7 +4288,7 @@ class learnpath
* Open a resource = initialise all local variables relative to this resource. Depending on the child
* class, this might be redefined to allow several behaviours depending on the document type.
*
* @param int Resource ID
* @param int $id Resource ID
*/
public function open($id)
{
@ -4304,8 +4313,8 @@ class learnpath
*
* @param int $itemId Optional item ID. If none given, uses the current open item.
*
* @return bool true if prerequisites are matched, false otherwise -
* Empty string if true returned, prerequisites string otherwise
* @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
* string otherwise
*/
public function prerequisites_match($itemId = null)
{
@ -4397,8 +4406,8 @@ class learnpath
* to normal users.
* Can be used as abstract.
*
* @param int Learnpath ID
* @param int New visibility
* @param int $lp_id Learnpath ID
* @param int $set_visibility New visibility
*
* @return bool
*/
@ -4426,6 +4435,11 @@ class learnpath
* @param int $id
* @param int $visibility
*
* @throws \Doctrine\ORM\NonUniqueResultException
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \Doctrine\ORM\TransactionRequiredException
*
* @return bool
*/
public static function toggleCategoryVisibility($id, $visibility = 1)
@ -4560,6 +4574,11 @@ class learnpath
* @param int $id
* @param int $setVisibility
*
* @throws \Doctrine\ORM\NonUniqueResultException
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \Doctrine\ORM\TransactionRequiredException
*
* @return bool
*/
public static function toggleCategoryPublish($id, $setVisibility = 1)
@ -4759,8 +4778,7 @@ class learnpath
/**
* Restart the whole learnpath. Return the URL of the first element.
* Make sure the results are saved with anoter method. This method should probably be
* redefined in children classes.
* Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
* To use a similar method statically, use the create_new_attempt() method.
*
* @return bool
@ -5003,10 +5021,11 @@ class learnpath
/**
* Sets the encoding.
*
* @param string New encoding
* @param string $enc New encoding
*
* @return bool
* TODO (as of Chamilo 1.8.8): Check in the future whether this method is needed.
*
* @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
*/
public function set_encoding($enc = 'UTF-8')
{
@ -5037,7 +5056,7 @@ class learnpath
* Sets the JS lib setting in the database directly.
* This is the JavaScript library file this lp needs to load on startup.
*
* @param string Proximity setting
* @param string $lib Proximity setting
*
* @return bool True on update success. False otherwise.
*/
@ -5064,7 +5083,7 @@ class learnpath
/**
* Sets the name of the LP maker (publisher) (and save).
*
* @param string Optional string giving the new content_maker of this learnpath
* @param string $name Optional string giving the new content_maker of this learnpath
*
* @return bool True
*/
@ -5137,8 +5156,8 @@ class learnpath
/**
* Set index specified prefix terms for all items in this path.
*
* @param string Comma-separated list of terms
* @param string Xapian term prefix
* @param string $terms_string Comma-separated list of terms
* @param string $prefix Xapian term prefix
*
* @return bool False on error, true otherwise
*/
@ -5215,7 +5234,7 @@ class learnpath
/**
* Sets the theme of the LP (local/remote) (and save).
*
* @param string Optional string giving the new theme of this learnpath
* @param string $name Optional string giving the new theme of this learnpath
*
* @return bool Returns true if theme name is not empty
*/
@ -5241,7 +5260,7 @@ class learnpath
/**
* Sets the image of an LP (and save).
*
* @param string Optional string giving the new image of this learnpath
* @param string $name Optional string giving the new image of this learnpath
*
* @return bool Returns true if theme name is not empty
*/
@ -5268,7 +5287,7 @@ class learnpath
/**
* Sets the author of a LP (and save).
*
* @param string Optional string giving the new author of this learnpath
* @param string $name Optional string giving the new author of this learnpath
*
* @return bool Returns true if author's name is not empty
*/
@ -5293,7 +5312,7 @@ class learnpath
/**
* Sets the hide_toc_frame parameter of a LP (and save).
*
* @param int 1 if frame is hidden 0 then else
* @param int $hide 1 if frame is hidden 0 then else
*
* @return bool Returns true if author's name is not empty
*/
@ -5323,7 +5342,7 @@ class learnpath
/**
* Sets the prerequisite of a LP (and save).
*
* @param int integer giving the new prerequisite of this learnpath
* @param int $prerequisite integer giving the new prerequisite of this learnpath
*
* @return bool returns true if prerequisite is not empty
*/
@ -5348,7 +5367,7 @@ class learnpath
/**
* Sets the location/proximity of the LP (local/remote) (and save).
*
* @param string Optional string giving the new location of this learnpath
* @param string $name Optional string giving the new location of this learnpath
*
* @return bool True on success / False on error
*/
@ -5378,7 +5397,7 @@ class learnpath
/**
* Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
*
* @param int DB ID of the item
* @param int $id DB ID of the item
*/
public function set_previous_item($id)
{
@ -5421,6 +5440,8 @@ class learnpath
*
* @param string $expired_on Optional string giving the new author of this learnpath
*
* @throws \Doctrine\ORM\OptimisticLockException
*
* @return bool Returns true if author's name is not empty
*/
public function set_expired_on($expired_on)
@ -5461,6 +5482,8 @@ class learnpath
*
* @param string $publicated_on Optional string giving the new author of this learnpath
*
* @throws \Doctrine\ORM\OptimisticLockException
*
* @return bool Returns true if author's name is not empty
*/
public function set_publicated_on($publicated_on)
@ -5521,7 +5544,7 @@ class learnpath
/**
* Sets the object's error message.
*
* @param string Error message. If empty, reinits the error string
* @param string $error Error message. If empty, reinits the error string
*/
public function set_error_msg($error = '')
{
@ -6143,8 +6166,8 @@ class learnpath
}
/**
* @param string string $update_audio
* @param bool $drop_element_here
* @param string $update_audio
* @param bool $drop_element_here
*
* @return string
*/
@ -6182,7 +6205,7 @@ class learnpath
}
$this->tree_array($arrLP);
$arrLP = isset($this->arrMenu) ? $this->arrMenu : null;
$arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
unset($this->arrMenu);
$default_data = null;
$default_content = null;
@ -7221,6 +7244,9 @@ class learnpath
*
* @param int $item_id
*
* @throws Exception
* @throws HTML_QuickForm_Error
*
* @return string
*/
public function display_edit_item($item_id)
@ -7345,6 +7371,9 @@ class learnpath
* Function that displays a list with al the resources that
* could be added to the learning path.
*
* @throws Exception
* @throws HTML_QuickForm_Error
*
* @return bool
*/
public function display_resources()
@ -7415,7 +7444,10 @@ class learnpath
/**
* Displays a document by id.
*
* @param int $id
* @param int $id
* @param bool $show_title
* @param bool $iframe
* @param bool $edit_link
*
* @return string
*/
@ -7448,6 +7480,8 @@ class learnpath
* @param int $id Item ID if already exists
* @param mixed $extra_info Extra information (quiz ID if integer)
*
* @throws Exception
*
* @return string HTML form
*/
public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
@ -7506,7 +7540,7 @@ class learnpath
}
$this->tree_array($arrLP);
$arrLP = isset($this->arrMenu) ? $this->arrMenu : null;
$arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
unset($this->arrMenu);
$form = new FormValidator(
@ -7663,9 +7697,9 @@ class learnpath
/**
* Addition of Hotpotatoes tests.
*
* @param string Action
* @param int Internal ID of the item
* @param mixed Extra information - can be an array with title and description indexes
* @param string $action
* @param int $id Internal ID of the item
* @param string $extra_info
*
* @return string HTML structure to display the hotpotatoes addition formular
*/
@ -7858,9 +7892,11 @@ class learnpath
/**
* Return the form to display the forum edit/add option.
*
* @param string Action (add/edit)
* @param int ID of the lp_item if already exists
* @param mixed Forum ID or title
* @param string $action
* @param int $id ID of the lp_item if already exists
* @param string $extra_info
*
* @throws Exception
*
* @return string HTML form
*/
@ -7918,7 +7954,7 @@ class learnpath
}
$this->tree_array($arrLP);
$arrLP = isset($this->arrMenu) ? $this->arrMenu : null;
$arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
unset($this->arrMenu);
if ($action == 'add') {
@ -8074,9 +8110,11 @@ class learnpath
/**
* Return HTML form to add/edit forum threads.
*
* @param string Action (add/edit)
* @param int Item ID if already exists in learning path
* @param mixed Extra information (thread ID if integer)
* @param string $action
* @param int $id Item ID if already exists in learning path
* @param string $extra_info
*
* @throws Exception
*
* @return string HTML form
*/
@ -8135,7 +8173,7 @@ class learnpath
}
$this->tree_array($arrLP);
$arrLP = isset($this->arrMenu) ? $this->arrMenu : null;
$arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
unset($this->arrMenu);
$form = new FormValidator(
@ -8301,11 +8339,14 @@ class learnpath
/**
* Return the HTML form to display an item (generally a dir item).
*
* @param string Item type (dir)
* @param string Title (optional, only when creating)
* @param string Action ('add'/'edit')
* @param int lp_item ID
* @param mixed Extra info
* @param string $item_type
* @param string $title
* @param string $action
* @param int $id
* @param string $extra_info
*
* @throws Exception
* @throws HTML_QuickForm_Error
*
* @return string HTML form
*/
@ -8506,7 +8547,10 @@ class learnpath
);
}
$renderer = $form->defaultRenderer();
$renderer->setElementTemplate('<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}', 'content_lp');
$renderer->setElementTemplate(
'<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
'content_lp'
);
$relative_prefix = '';
@ -8554,6 +8598,9 @@ class learnpath
* @param int $id ID of the lp_item (if already exists)
* @param mixed $extra_info Integer if document ID, string if info ('new')
*
* @throws Exception
* @throws HTML_QuickForm_Error
*
* @return string HTML form
*/
public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
@ -8976,6 +9023,9 @@ class learnpath
* @param int $id Item ID if exists
* @param mixed $extra_info
*
* @throws Exception
* @throws HTML_QuickForm_Error
*
* @return string HTML form
*/
public function display_link_form($action = 'add', $id = 0, $extra_info = '')
@ -9039,7 +9089,7 @@ class learnpath
}
$this->tree_array($arrLP);
$arrLP = isset($this->arrMenu) ? $this->arrMenu : null;
$arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
unset($this->arrMenu);
if ($action == 'add') {
@ -9190,9 +9240,11 @@ class learnpath
/**
* Return HTML form to add/edit a student publication (work).
*
* @param string Action (add/edit)
* @param int Item ID if already exists
* @param mixed Extra info (work ID if integer)
* @param string $action
* @param int $id Item ID if already exists
* @param string $extra_info
*
* @throws Exception
*
* @return string HTML form
*/
@ -9252,7 +9304,7 @@ class learnpath
}
$this->tree_array($arrLP);
$arrLP = isset($this->arrMenu) ? $this->arrMenu : null;
$arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
unset($this->arrMenu);
$form = new FormValidator('frm_student_publication', 'post', '#');
@ -9400,7 +9452,7 @@ class learnpath
/**
* Displays the menu for manipulating a step.
*
* @param $item_id
* @param id $item_id
* @param string $item_type
*
* @return string
@ -9585,6 +9637,9 @@ class learnpath
*
* @param int $item_id Item ID
*
* @throws Exception
* @throws HTML_QuickForm_Error
*
* @return string HTML form
*/
public function display_move_item($item_id)
@ -9651,6 +9706,9 @@ class learnpath
* @param string $title
* @param array $data
*
* @throws Exception
* @throws HTML_QuickForm_Error
*
* @return string
*/
public function display_item_small_form($item_type, $title = '', $data = [])
@ -9673,7 +9731,7 @@ class learnpath
*
* @todo use FormValidator
*
* @param int Item ID
* @param int Item ID
*
* @return string HTML form
*/
@ -9737,7 +9795,6 @@ class learnpath
'min_score' => $row['min_score'],
'mastery_score' => $row['mastery_score'],
'prerequisite' => $row['prerequisite'],
'next_item_id' => $row['next_item_id'],
'display_order' => $row['display_order'],
'prerequisite_min_score' => $row['prerequisite_min_score'],
'prerequisite_max_score' => $row['prerequisite_max_score'],
@ -9745,7 +9802,7 @@ class learnpath
}
$this->tree_array($arrLP);
$arrLP = isset($this->arrMenu) ? $this->arrMenu : null;
$arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
unset($this->arrMenu);
for ($i = 0; $i < count($arrLP); $i++) {
@ -9782,27 +9839,64 @@ class learnpath
if ($item['item_type'] == TOOL_QUIZ) {
// lets update max_score Quiz information depending of the Quiz Advanced properties
$tmp_obj_lp_item = new LpItem($course_id, $item['id']);
$tmp_obj_exercice = new Exercise($course_id);
$tmp_obj_exercice->read($tmp_obj_lp_item->path);
$tmp_obj_lp_item->max_score = $tmp_obj_exercice->get_max_score();
$tmp_obj_lp_item->update();
$item['max_score'] = $tmp_obj_lp_item->max_score;
$lpItemObj = new LpItem($course_id, $item['id']);
$exercise = new Exercise($course_id);
$exercise->read($lpItemObj->path);
$lpItemObj->max_score = $exercise->get_max_score();
$lpItemObj->update();
$item['max_score'] = $lpItemObj->max_score;
$return .= '<td>';
$return .= '<input class="form-control" size="4" maxlength="3" name="min_'.$item['id'].'" type="number" min="0" step="1" max="'.$item['max_score'].'" value="'.$selectedMinScoreValue.'" />';
$return .= '<input
class="form-control"
size="4" maxlength="3"
name="min_'.$item['id'].'"
type="number"
min="0"
step="1"
max="'.$item['max_score'].'"
value="'.$selectedMinScoreValue.'"
/>';
$return .= '</td>';
$return .= '<td>';
$return .= '<input class="form-control" size="4" maxlength="3" readonly name="max_'.$item['id'].'" type="number" min="0" step="1" max="'.$item['max_score'].'" value="'.$selectedMaxScoreValue.'" />';
$return .= '<input
class="form-control"
size="4"
maxlength="3"
name="max_'.$item['id'].'"
type="number"
min="0"
step="1"
max="'.$item['max_score'].'"
value="'.$selectedMaxScoreValue.'"
/>';
$return .= '</td>';
}
if ($item['item_type'] == TOOL_HOTPOTATOES) {
$return .= '<td>';
$return .= '<input size="4" maxlength="3" name="min_'.$item['id'].'" type="number" min="0" step="1" max="'.$item['max_score'].'" value="'.$selectedMinScoreValue.'" />';
$return .= '<input
size="4"
maxlength="3"
name="min_'.$item['id'].'"
type="number"
min="0"
step="1"
max="'.$item['max_score'].'"
value="'.$selectedMinScoreValue.'"
/>';
$return .= '</td>';
$return .= '<td>';
$return .= '<input size="4" maxlength="3" name="max_'.$item['id'].'" readonly type="number" min="0" step="1" max="'.$item['max_score'].'" value="'.$selectedMaxScoreValue.'" />';
$return .= '<input
size="4"
maxlength="3"
name="max_'.$item['id'].'"
type="number"
min="0"
step="1"
max="'.$item['max_score'].'"
value="'.$selectedMaxScoreValue.'"
/>';
$return .= '</td>';
}
$return .= '</tr>';
@ -9813,7 +9907,8 @@ class learnpath
$return .= '</table>';
$return .= '</div>';
$return .= '<div class="form-group">';
$return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.get_lang('ModifyPrerequisites').'</button>';
$return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
get_lang('ModifyPrerequisites').'</button>';
$return .= '</form>';
return $return;
@ -9822,8 +9917,6 @@ class learnpath
/**
* Return HTML list to allow prerequisites selection for lp.
*
* @param int Item ID
*
* @return string HTML form
*/
public function display_lp_prerequisites_list()
@ -9864,6 +9957,9 @@ class learnpath
*
* @param bool $showInvisibleFiles
*
* @throws Exception
* @throws HTML_QuickForm_Error
*
* @return string
*/
public function get_documents($showInvisibleFiles = false)
@ -11421,7 +11517,7 @@ EOD;
/**
* Uploads an author image to the upload/learning_path/images directory.
*
* @param array The image array, coming from the $_FILES superglobal
* @param array The image array, coming from the $_FILES superglobal
*
* @return bool True on success, false on error
*/
@ -11674,6 +11770,8 @@ EOD;
/**
* @param array $params
*
* @throws \Doctrine\ORM\OptimisticLockException
*/
public static function createCategory($params)
{
@ -11687,6 +11785,10 @@ EOD;
/**
* @param array $params
*
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \Doctrine\ORM\TransactionRequiredException
*/
public static function updateCategory($params)
{
@ -11702,6 +11804,10 @@ EOD;
/**
* @param int $id
*
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \Doctrine\ORM\TransactionRequiredException
*/
public static function moveUpCategory($id)
{
@ -11719,6 +11825,10 @@ EOD;
/**
* @param int $id
*
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \Doctrine\ORM\TransactionRequiredException
*/
public static function moveDownCategory($id)
{
@ -11737,6 +11847,8 @@ EOD;
/**
* @param int $courseId
*
* @throws \Doctrine\ORM\Query\QueryException
*
* @return int|mixed
*/
public static function getCountCategories($courseId)
@ -11778,6 +11890,10 @@ EOD;
/**
* @param int $id
*
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \Doctrine\ORM\TransactionRequiredException
*
* @return CLpCategory
*/
public static function getCategory($id)
@ -11807,6 +11923,10 @@ EOD;
/**
* @param int $id
*
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \Doctrine\ORM\TransactionRequiredException
*
* @return mixed
*/
public static function deleteCategory($id)
@ -12349,6 +12469,9 @@ EOD;
/**
* Get the LP Final Item form.
*
* @throws Exception
* @throws HTML_QuickForm_Error
*
* @return string
*/
public function getFinalItemForm()
@ -12707,9 +12830,9 @@ EOD;
*
* @author Yannick Warnier <ywarnier@beeznest.org>
*
* @param string Course code
* @param string The tool type (using constants declared in main_api.lib.php)
* @param int The resource ID
* @param string $course_code Course code
* @param string $learningPathId The tool type (using constants declared in main_api.lib.php)
* @param int $id_in_path The resource ID
*
* @return string
*/

@ -103,10 +103,8 @@ class learnpathItem
$user_id = api_get_user_id();
}
if (self::DEBUG > 0) {
error_log(
"learnpathItem constructor: id: $id user_id: ".
"$user_id course_id: $course_id item_content: ".print_r($item_content, 1)
);
error_log("learnpathItem constructor: id: $id user_id: $user_id course_id: $course_id");
error_log("item_content: ".print_r($item_content, 1));
}
$id = intval($id);
if (empty($item_content)) {
@ -1989,8 +1987,8 @@ class learnpathItem
}
$restart = 1;
$mystatus = $this->get_status(true);
if ($this->get_prevent_reinit() > 0
) { // If prevent_reinit == 1 (or more)
if ($this->get_prevent_reinit() > 0) {
// If prevent_reinit == 1 (or more)
// If status is not attempted or incomplete, authorize retaking (of the same) anyway. Otherwise:
if ($mystatus != $this->possible_status[0] && $mystatus != $this->possible_status[1]) {
$restart = -1;
@ -2113,6 +2111,7 @@ class learnpathItem
return false;
}
while (strpos($prereqs_string, '(') !== false) {
// Remove any () set and replace with its value.
$matches = [];
@ -2197,7 +2196,6 @@ class learnpathItem
}
} else {
// No ORs found, now look for ANDs.
if (self::DEBUG > 1) {
error_log('New LP - Didnt find any AND, looking for =', 0);
}
@ -2438,7 +2436,7 @@ class learnpathItem
}
} else {
// Nothing found there either. Now return the
// value of the corresponding resource completion status.
// value of the corresponding resource completion status.
if (self::DEBUG > 1) {
error_log(
'New LP - Didnt find any group, returning value for '.$prereqs_string,
@ -2586,12 +2584,8 @@ class learnpathItem
if ($returnstatus && $this->prevent_reinit == 1) {
// I would prefer check in the database.
$lp_item_view = Database::get_course_table(
TABLE_LP_ITEM_VIEW
);
$lp_view = Database::get_course_table(
TABLE_LP_VIEW
);
$lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
$lp_view = Database::get_course_table(TABLE_LP_VIEW);
$sql = 'SELECT iid FROM '.$lp_view.'
WHERE
@ -2601,9 +2595,7 @@ class learnpathItem
session_id = '.$sessionId.'
LIMIT 0, 1';
$rs_lp = Database::query($sql);
$lp_id = Database::fetch_row(
$rs_lp
);
$lp_id = Database::fetch_row($rs_lp);
$my_lp_id = $lp_id[0];
$sql = 'SELECT status FROM '.$lp_item_view.'
@ -2706,16 +2698,13 @@ class learnpathItem
$status = $items[$refs_list[$list[0]]]->get_status(true);
$returnstatus = $status == 'completed' || $status == 'passed';
if (!$returnstatus && empty($this->prereq_alert)) {
$this->prereq_alert = get_lang(
'LearnpathPrereqNotCompleted'
);
$this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
}
return $returnstatus;
}
}
}
if (empty($this->prereq_alert)) {
$this->prereq_alert = get_lang('LearnpathPrereqNotCompleted');
}

@ -4,7 +4,6 @@
/**
* @package chamilo.learnpath
*
* @author Yannick Warnier <ywarnier@beeznest.org>
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
echo time();

@ -46,7 +46,7 @@ $htmlHeadXtra[] = '<script>
function setFocus(){
$("#idTitle").focus();
}
$(window).load(function () {
$(window).on("load", function () {
setFocus();
});
</script>';
@ -997,8 +997,12 @@ switch ($action) {
$hide_toc_frame = null;
}
$_SESSION['oLP']->set_hide_toc_frame($hide_toc_frame);
$_SESSION['oLP']->set_prerequisite($_REQUEST['prerequisites']);
$_SESSION['oLP']->set_use_max_score($_REQUEST['use_max_score']);
$_SESSION['oLP']->set_prerequisite(
isset($_POST['prerequisites']) ? (int) $_POST['prerequisites'] : 0
);
$_SESSION['oLP']->set_use_max_score(
isset($_POST['use_max_score']) ? 1 : 0
);
$subscribeUsers = isset($_REQUEST['subscribe_users']) ? 1 : 0;
$_SESSION['oLP']->setSubscribeUsers($subscribeUsers);

@ -8,7 +8,7 @@ use ChamiloSession as Session;
*
* @package chamilo.learnpath
*
* @author Yannick Warnier <ywarnier@beeznest.org>
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
@ -242,8 +242,12 @@ if ($enableLpExtraFields) {
</script>';
}
$defaults['publicated_on'] = !empty($publicated_on) && $publicated_on !== '0000-00-00 00:00:00' ? api_get_local_time($publicated_on) : null;
$defaults['expired_on'] = (!empty($expired_on)) ? api_get_local_time($expired_on) : date('Y-m-d 12:00:00', time() + 84600);
$defaults['publicated_on'] = !empty($publicated_on) && $publicated_on !== '0000-00-00 00:00:00'
? api_get_local_time($publicated_on)
: null;
$defaults['expired_on'] = (!empty($expired_on))
? api_get_local_time($expired_on)
: date('Y-m-d 12:00:00', time() + 84600);
$defaults['subscribe_users'] = $learnPath->getSubscribeUsers();
$defaults['skills'] = array_keys($skillList);
$form->setDefaults($defaults);

@ -117,7 +117,6 @@ if ($accessGranted == false) {
$downloadCertificateLink,
$badgeLink
);
// TODO: Missing validation of learning path completion
} else {
// A gradebook was found, proceed...
/** @var Category $category */

@ -39,7 +39,7 @@ if (!api_is_allowed_to_edit(null, true) && intval($visibility) == 0) {
}
/** @var learnpath $lp */
$lp = Session::read('oLP');
if ($lp) {
if (!$lp) {
api_not_allowed(true);
}

@ -176,7 +176,7 @@ $data = [];
foreach ($categories as $item) {
$categoryId = $item->getId();
if (!learnpath::categoryIsVisibleForStudent($item, $user)) {
if ($user && !learnpath::categoryIsVisibleForStudent($item, $user)) {
continue;
}

@ -2034,7 +2034,6 @@ function xajax_save_objectives(lms_lp_id,lms_user_id,lms_view_id,lms_item_id,ite
* @uses lp_ajax_switch_item.php
*/
function xajax_switch_item_details(lms_lp_id,lms_user_id,lms_view_id,lms_item_id,next_item) {
var params = {
'lid': lms_lp_id,
'uid': lms_user_id,
@ -2157,7 +2156,6 @@ function attach_glossary_into_scorm(type) {
var complex_array = new Array();
$("iframe").contents().find("body").on("click", ".glossary-ajax", function() {
div_show_id="div_show_id";
div_content_id="div_content_id";
@ -2260,7 +2258,6 @@ function attach_glossary_into_scorm(type) {
objects.each(function (value, obj) {
var dialogId = this.id +'_dialog';
var openerId = this.id +'_opener';
var link = '<a id="'+openerId+'" href="#" class="generated btn">'+
'<div style="text-align: center"><img src="<?php echo Display::returnIconPath('play-circle-8x.png'); ?>"/><br />If video does not work, try clicking here.</div></a>';
var embed = $("iframe").contents().find("#"+this.id).find('embed').first();
@ -2373,6 +2370,5 @@ function attach_glossary_into_scorm(type) {
});
});
}
}
}

@ -604,7 +604,7 @@ if (api_is_allowed_to_edit(null, true)) {
Display::return_icon('export_excel.png', get_lang('ExportAsXLS'), [], ICON_SIZE_MEDIUM).'</a> ';
if ($canEditUsers) {
$actions .= '<a href="user_import.php?'.api_get_cidreq().'&action=import">'.
$actions .= '<a href="user_import.php?'.api_get_cidreq().'&action=import&type='.$type.'">'.
Display::return_icon('import_csv.png', get_lang('ImportUsersToACourse'), [], ICON_SIZE_MEDIUM).'</a> ';
}

Loading…
Cancel
Save