diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 974355a..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index faa1838..5269b90 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .vscode - +.DS_Store +venv/ +__pycache__/ +*.pyc diff --git a/Admin.php b/Admin.php index 8280aa5..ae92238 100644 --- a/Admin.php +++ b/Admin.php @@ -1,293 +1,289 @@ - - - - - - - - - - -
-
-

Administration Panel

-
- -
-
- - -
- - TA Account Management -
" ; - echo "TA Accounts
" ; - } - else if($_SESSION['user_type'] == "Admin"){ - echo "

Lecturer Account Management

-
"; - echo "Lecturer Accounts
"; - } - - ?> - -
- - - - - -
- -

- - Create TA Accounts "; - - } - else if($_SESSION['user_type'] == "Admin"){ - echo "Create Lecturer Accounts "; - } - - ?> -
- - Full Name - - Email - - - Passport Number / ID (Used as Initial Password) - -
User Type : - - TA (Teaching Assistant) '; - - } - else if($_SESSION['user_type'] == "Admin"){ - - echo " Lecturer "; - - } - - ?> - -
- - '; - $_SESSION['info_Admin_Users'] = null; - } - if (isset($_SESSION['info_Admin_Users'])) { - echo '
'; - $_SESSION['info_Admin_Users'] = null; - } - - ?> - -
- -
- -
- - - - - - - -
-
- -
- -
- -
- - - - - -
- - - -
- -
- - \ No newline at end of file + + + + + + + +
+ + +
+

Administration panel

+ +
+
+ +
+ + + + + +
+ + +
+

Copy & paste student number to the following box, and separate two student numbers with a space.

+
+ + +
+
+ +

+ + Create TA Accounts

"; + } + else if($_SESSION['user_type'] == "Admin"){ + echo "

Create Lecturer Accounts

"; + } + + ?> +
+ + Full name +
+ Email +
+ Initial password (Enter a strong password or leave it empty to let LRR generate one) +
+ User type: + TA (Teaching Assistant) '; + } else if ($_SESSION['user_type'] == "Admin"){ + echo " Lecturer "; + } + + ?> + +

+ + + '; + $_SESSION['info_Admin_Users'] = null; + } + ?> + +
+ +
+ + +

+ + + + + + + + + + console.log('here {$user_id}');"; // debug trick + // find the TAs in the courses taught by this instructor + $ta_result = mysqli_query( + $con, + "SELECT TA FROM course_ta INNER JOIN courses_table ON course_ta.Course_ID=courses_table.Course_ID WHERE courses_table.Lecturer_User_ID=$user_id" + ); + $ta_ids = array(-1); // -1 is non-existent ID + while ($row = mysqli_fetch_assoc($ta_result)) { + array_push($ta_ids, $row['TA']); + } + $ta_ids2 = implode(', ', $ta_ids); + $result = mysqli_query( + $con, + "SELECT * FROM users_table WHERE UserType in ('TA') and User_ID in ($ta_ids2)" + ); + } + + else if ($_SESSION['user_type'] == "Admin"){ + $result = mysqli_query( + $con, + "SELECT * FROM users_table WHERE UserType in ('Lecturer')" + ); + } + + $num_rows = 0; + while ($row = mysqli_fetch_assoc($result)) { + $pass = $row['Password']; + $btn = ""; + if ($row['Status'] == "Active") { + $newstatus = "Blocked"; + $btnBlock = ""; + } else { + $newstatus = "Active"; + $btnBlock = ""; + } + + echo ""; + $num_rows += 1; + } + if ($num_rows == 0) { + echo "

No TA

"; + } + ?> +
IDNameEmailReset password Block/Activate
" . $row['User_ID'] . "" . $row['Full_Name'] . "" . $row['Email'] . "$btn$btnBlock
+
+ +
+ +
+ +
+ + + +

+ +

Past courses

+ + + + + + + + + + + + "; + } + } + ?> + +
Course nameFacultyLecturerTAsAssign a new TA
$code - $name $faculty $lecturer$ta
+ +
+ +
+ +
+ +
+ + + + + + + + + + + + diff --git a/AnswerSecurityQuestions.php b/AnswerSecurityQuestions.php new file mode 100644 index 0000000..7c78db4 --- /dev/null +++ b/AnswerSecurityQuestions.php @@ -0,0 +1,87 @@ +'; + } + } + + // Fetch security questions from the database for display + $sql = "SELECT question1, question2 FROM password_recovery_security_questions WHERE email = '$email'"; + $result = mysqli_query($con, $sql); + + if ($row = mysqli_fetch_assoc($result)) { + // Display the questions in a form + echo'


'; + echo '
'; + echo '
'; + echo '
'; + echo '
'; + if (isset($error_message)) { + echo ''; // Display error message + } + + //echo '
'; + echo '
'; + echo 'Answer Your Security Questions.'; + + // Question 1 + echo '
'; + echo ''; + echo ''; + echo '
'; + echo'
'; + + // Question 2 + echo '
'; + echo ''; + echo ''; + echo '
'; + + echo ''; + echo '
'; + echo '
'; // Close container + } else { + echo '
'; + } +} else { + header("Location: RecoverPassword.php"); // Redirect if session data is missing + exit; +} + +mysqli_close($con); +?> diff --git a/Course.php b/Course.php index 5dc8583..c469163 100644 --- a/Course.php +++ b/Course.php @@ -6,760 +6,734 @@ include 'NoDirectPhpAcess.php'; + +
+ + - Courses > $name ($code) > Lab Reports
Faculty: $faculty | Year: $academic | Lecturer: $lecturer -
"; - } else { - $ta_name = ""; - while ($row = mysqli_fetch_assoc($ta_result)) { - $ta_name = $ta_name.$row['Full_Name']." "; - } - $ta_name = trim ($ta_name); - echo "
- Courses > $name ($code) > Lab Reports
Faculty: $faculty | Year: $academic | Lecturer: $lecturer | Teaching Assistant: $ta_name -
"; + echo "No course matching the given course URL: ".$course_url; + + } else { + while ($row = mysqli_fetch_assoc($result)) { + $name = $row['Course_Name']; + $code = $row['Course_Code']; + $faculty = $row['Faculty']; + $lecturer = $row['Full_Name']; + $academic = $row['Academic_Year']; + $url = $row['URL']; + $course_id = $row['Course_ID']; + // also get teaching assistant names (if any) + $ta_result = mysqli_query($con, "SELECT Full_Name FROM users_table WHERE User_ID IN (SELECT TA FROM course_ta WHERE Course_ID='$course_id');"); + if (mysqli_num_rows($ta_result) == 0) { + echo "
+ Courses > ($code) $name > Assignments
+ Faculty: $faculty   Year: $academic   Lecturer: $lecturer +
"; + } else { + $ta_name = ""; + while ($row = mysqli_fetch_assoc($ta_result)) { + $ta_name = $ta_name.$row['Full_Name']." "; + } + $ta_name = trim($ta_name); + echo "
+ Courses > ($code) $name > Assignments
+ Faculty: $faculty   Year: $academic   Lecturer: $lecturer   Teaching Assistant: $ta_name +
"; + } } - } + } } -} -?> + ?> + + + ' . $_SESSION['info_general'] . ''; + $_SESSION['info_general'] = null; + } + + if (isset($_SESSION['info_courses'])) { + echo '
' . $_SESSION['info_courses'] . '
'; + $_SESSION['info_courses'] = null; + } + ?> + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + +
+
+ +
+ +

My groups

+ + Create group"; + ?> + +
+ + Invite member "; + + if($status == "Invited") + { + $extra2 = " Accept"; + $extra3 = " Decline"; + } + + echo ""; + } + } + ?> +
+ +
+ + -
- -'; - $_SESSION['info_general']=null; -} - -if (isset($_SESSION['info_courses'])) { - echo '
'; - $_SESSION['info_courses']=null; -} -?> -
+ + + - -
- -
- -
- - - - - -
- - - - - - - - - - - - - - - - - - - - -
- -
- -
-

Class Groups

- - Create Group"; - - ?> - - - -
- Invite Others"; - - if($status=="Invited") - { - $extra2=" Accept"; - $extra3=" Decline"; - - } - - # Add "delete group" button and allow only group creator to delete it - $extra4 = ""; - - echo "
$name ($status) $extra $extra2 $extra3" . - (($status == "Created")? "$extra4": "") - ."
"; - - $rs2=mysqli_query($con,"SELECT `ID`, `Course_Group_id`, course_group_members_table.Student_ID, - course_group_members_table.`Status`,users_table.Full_Name FROM `course_group_members_table` -INNER JOIN users_table on users_table.Student_ID=course_group_members_table.Student_ID -where course_group_members_table.Course_Group_id=$id"); - - #Check whether the current user in session is the creator of the group - $rs3 = mysqli_query($con, "SELECT `Status` from course_group_members_table where Student_ID = $student_id"); - $flag = mysqli_fetch_assoc($rs3)['Status'] == "Created"; - - while($row = mysqli_fetch_assoc($rs2)) { - $name=$row['Full_Name']; - $id=$row['Course_Group_id']; - $status=$row['Status']; - $Student_ID=$row['Student_ID']; - - #Show group members + remove button next to each member except the creator of the group - if($flag){ - echo "
  • $name-$Student_ID ($status)".(($status != "Created")?"":"")."
  • "; - }else{ - echo "
  • $name-$Student_ID ($status)"; - } - - } - - - - - - - - } - } - ?> - - - - - -
  • - -
    - - - - - - - - - - - + + + + diff --git a/Courses.php b/Courses.php index 5bba2fc..2d939a8 100644 --- a/Courses.php +++ b/Courses.php @@ -4,110 +4,99 @@ include 'NoDirectPhpAcess.php'; -$user_d = $_SESSION['user_id']; +
    -if( $_SESSION['user_type']=="Lecturer" || $_SESSION['user_type']=="TA") -{ + + - +
    + + + -
    - - - - - - - - - - -
    - ($code) - $name -
    Faculty: $faculty | Year: $academic | Lecturer: $lecturer -
    -
    - "; - - echo "
    "; + + + 0) { + while ($row = mysqli_fetch_assoc($result)) { + $name = $row['Course_Name']; + $code = $row['Course_Code']; + $faculty = $row['Faculty']; + $lecturer = $row['Full_Name']; + $academic = $row['Academic_Year']; + $url = $row['URL']; + $id = $row['Course_ID']; + $course_id = $row['Course_ID']; + echo "

    ($code) - $name

    +
    + Faculty: $faculty    Year: $academic    Lecturer: $lecturer +
    +
    "; + echo "
    "; + } + + // ------------------------------Editing Lab Assignment by Lecturer ------------------------------------ - if($_GET['act']=="edit"){ + if ($_GET['act'] == "edit") { $getid = mysqli_real_escape_string($con, $_GET["cid"]); $result1 = mysqli_query($con, "SELECT * from lab_reports_table WHERE Lab_Report_ID = '$getid'"); - while($row1 = mysqli_fetch_assoc($result1)) { $Deadline = $row1['Deadline']; $_SESSION['Date'] = trim( strstr($Deadline, ' ', true) ); @@ -117,8 +106,8 @@ New Date/Time
    query($sql) === TRUE) { $_SESSION["info_Updated"]="Assignment information updated successfully."; - } else { - // echo "Error: " . $sql . "
    " . $con->error; - echo "Serious error happened whiling updating assignment information."; + echo "Serious error happened while updating assignment information."; } } - - if( $_SESSION['user_type']=="Lecturer"){ + + if (isset($_POST['form_deletelab'])) { + $sql = "DELETE FROM lab_reports_table WHERE Lab_Report_ID='$getid'"; + if ($con->query($sql) === TRUE) { + $_SESSION["info_Updated"]="Assignment deleted successfully."; + } else { + echo "Serious error happened while deleting the assignment."; + } + } + + + if ($_SESSION['user_type'] == "Lecturer") { $Date = $_SESSION['Date']; $Time = $_SESSION['Time']; $Instructions = $_SESSION['Instructions']; $Title = $_SESSION['Title']; $Marks = $_SESSION['Marks']; $Type = $_SESSION['Type']; - - echo "

    Editing Lab Assignment

    "; - ?> -
    - + echo "

    Edit assignment information

    "; + ?> + + - + Deadline Date/Time -
    -
    ">
    -
    ">
    +
    +
    ">
    +
    ">
    Title - "> + "> Instructions - + Marks - "> + "> Attachment 1 - - + Attachment 2 - - + Attachment 3 - - - + Attachment 4 - +
    - - Individual Group"; } else { echo "Submission Type Individual Group"; - } + } ?> +
    +
    +



    +
    + +
    +
    + -
    -



    - - if( $_SESSION['user_type']=="Lecturer"){ +

    New assignment

    - ?> - -

    Post new Lab Assignment

    - -
    - - + + + - - Deadline Date/Time -
    -
    -
    + + Deadline (date and time) +
    +
    +
    Title - - Instructions - - Marks - + + Instruction + + Mark + Attachment 1 - - + Attachment 2 - - + Attachment 3 - - - + Attachment 4 - +
    - Submission Type Individual + Submission type: Individual - Group + Group
    -
    -



    -"; - - echo "

    Lab Report Assignment list

    "; - - error_reporting(0); - if(isset($_SESSION["info_Updated"])){ - echo '
    '; - $_SESSION['info_Updated'] = null; +
    +



    + '; - $_SESSION['info_courses'] = null; - } - if (isset($_SESSION['info_courses'])) { - echo '
    '; - $_SESSION['info_courses']=null; - } - - - - - $result = mysqli_query($con," SELECT `Lab_Report_ID`,Type,Marks, `Course_ID`, `Posted_Date`, `Deadline`, `Instructions`, `Title`, `Attachment_link_1`, `Attachment_link_2`, `Attachment_link_3`, " - . "`Attachment_link_4` FROM `lab_reports_table` WHERE Course_ID=$id ORDER by Lab_Report_ID DESC"); - - - if( $_SESSION['user_type']=="TA") - { - echo "*Only Lecturer can post a new lab report assignment
    "; - } - if(mysqli_num_rows($result)==0) - { - echo "No assignments posted so far."; - - } else { while($row = mysqli_fetch_assoc($result)) { - $marks=$row['Marks']; - $title=$row['Title']; - $ins=$row['Instructions']; - $posted=$row['Posted_Date']; - $deadline=$row['Deadline']; - $att1=$row['Attachment_link_1']; - $att2=$row['Attachment_link_2']; - $att3=$row['Attachment_link_3']; - $att4=$row['Attachment_link_4']; - $id=$row['Lab_Report_ID']; - $cours_id=$row['Course_ID']; - $as_type=$row['Type']; - $full_link="$att1"; - - if($att2!=""){ - $full_link= $full_link."  |  $att2"; - } - if($att3!=""){ - $full_link= $full_link."  |  $att3"; - } - - if($att4!=""){ - $full_link= $full_link."   |   $att4"; - } - - - - - $resultx1 = mysqli_query($con,"Select Count(*) as cnt from lab_report_submissions where lab_report_submissions.Lab_Report_ID=$id"); - while($row = mysqli_fetch_assoc($resultx1)) {$count_subs=$row['cnt'];} - - $resultx2 = mysqli_query($con,"Select COUNT(*) as cnt from lab_report_submissions where lab_report_submissions.Lab_Report_ID=$id and Marks is not null"); - if(mysqli_num_rows($resultx2)==0){$count_marked=0;} else { while($row = mysqli_fetch_assoc($resultx2)) {$count_marked =$row['cnt'];}} - - - $header="Courses > ".$name."($code) > Assignments > ".$title; - - echo "
    - $title ($as_type)
    $ins - -
    Posted : $posted Deadline : $deadline   ($marks Marks)           " - . "
    " - - . "    $count_subs Submissions ( $count_marked Marked )       Edit   |   View    |   Extend Deadline
    Attachments : $full_link
    " - . "  
    - "; - - - - }} - echo "
    "; - - - - - - $resultx1 = mysqli_query($con,"SELECT course_students_table.Student_ID,users_table.Full_Name FROM -`course_students_table` -INNER JOIN users_table on users_table.Student_ID=course_students_table.Student_ID -WHERE Course_ID=$course_id"); - - - echo ""; - - - - - return; - - } - - ?> - - - - - "; - - - $result = mysqli_query($con,"SELECT `Course_ID`, `Course_Name`, `Academic_Year`, `Faculty`, " - . "`Lecturer_User_ID`, `TA_User_ID`, `Course_Code`, `URL`, `Verify_New_Members` , users_table.Full_Name FROM `courses_table` INNER JOIN users_table ON users_table.User_ID=courses_table.Lecturer_User_ID where courses_table.Lecturer_User_ID=$user_d"); - - if($_SESSION['user_type']=="TA") - { - $result = mysqli_query($con,"SELECT course_ta.Course_ID, `Course_Name`, - `Academic_Year`, `Faculty`, `Lecturer_User_ID`, `TA_User_ID`, `Course_Code`, `URL`, `Verify_New_Members` FROM `courses_table` -INNER JOIN -course_ta ON course_ta.Course_ID=courses_table.Course_ID where course_ta.TA=$user_d"); - - } - // $result = mysqli_query($con,"SELECT `Course_ID`, `Course_Name`, `Academic_Year`, `Faculty`, `Lecturer_User_ID`, `TA_User_ID`, `Course_Code`, `URL`, `Verify_New_Members` , users_table.Full_Name FROM `courses_table` INNER JOIN users_table ON users_table.User_ID=courses_table.Lecturer_User_ID"); - - - if(mysqli_num_rows($result)==0) - {} else { while($row = mysqli_fetch_assoc($result)) { - $id=$row['Course_ID']; - $name=$row['Course_Name']; - $code=$row['Course_Code']; - $faculty=$row['Faculty']; - $lecturer=$row['Full_Name']; - $academic=$row['Academic_Year']; - $url=$row['URL']; - - $resultTA = mysqli_query($con,"SELECT `Course_ID`, `TA`,users_table.Full_Name as TA_NAME FROM `course_ta` -INNER JOIN users_table on users_table.User_ID=course_ta.TA -where course_ta.Course_ID=$id"); - - $ta=""; - while($rowTA = mysqli_fetch_assoc($resultTA)) { - $ta=$ta." - ".$rowTA['TA_NAME']; } - - - - echo" - -
    - ($code) - $name -
    Faculty : $faculty        Year : $academic        Lecturer :$lecturer        TA:$ta -
    - "; - - }}?> -
    -
    -
    - Course Joining Requests - - - No Course joining request so far for all your courses
    "; - } else { while($row = mysqli_fetch_assoc($result)) { - $id=$row['ID']; - - $name=$row['Course_Name']; - $code=$row['Course_Code']; - $faculty=$row['Faculty']; - $std_name=$row['Full_Name']; - $academic=$row['Academic_Year']; - - echo "
    - $std_name is Requesting to join
    [($code) - $name ]     
    Accept -    Decline -
    "; - - - } - } - ?> + echo "
    "; + echo "

    Assignment list

    "; + error_reporting(0); + if (isset($_SESSION["info_Updated"])) { + echo '
    '; + $_SESSION['info_Updated'] = null; + } + if (isset($_SESSION['info_courses'])) { + echo '
    '; + $_SESSION['info_courses'] = null; + } + if (isset($_SESSION['info_courses'])) { + echo '
    '; + $_SESSION['info_courses']=null; + } + if( $_SESSION['user_type'] == "TA") { + echo "Only Lecturer can post assignments.
    "; + } + $result = mysqli_query($con, "SELECT Lab_Report_ID, Type, Marks, Course_ID, Posted_Date, Deadline, Instructions, Title, Attachment_link_1, Attachment_link_2, Attachment_link_3, Attachment_link_4 + FROM lab_reports_table + WHERE Course_ID=$id ORDER BY Lab_Report_ID DESC"); + if(mysqli_num_rows($result)==0) { + echo "No assignments posted so far."; + } else { + while ($row = mysqli_fetch_assoc($result)) { + $marks = $row['Marks']; + $title = $row['Title']; + $ins = $row['Instructions']; + $posted = $row['Posted_Date']; + $deadline = $row['Deadline']; + $att1 = $row['Attachment_link_1']; + $att2 = $row['Attachment_link_2']; + $att3 = $row['Attachment_link_3']; + $att4 = $row['Attachment_link_4']; + $id = $row['Lab_Report_ID']; + $cours_id = $row['Course_ID']; + $as_type = $row['Type']; + $full_link = "$att1"; + if ($att2 != "") { + $full_link = $full_link."  |  $att2"; + } + if ($att3 != "") { + $full_link = $full_link."  |  $att3"; + } + if ($att4 != "") { + $full_link = $full_link."   |   $att4"; + } - -Only Lecturers can Post new Lab report Assignments"; - } - if( $_SESSION['user_type']=="Lecturer"){ ?> - - Create new Course Portal - -
    - - - Course Name - + $resultx1 = mysqli_query($con, "SELECT COUNT(*) AS cnt FROM lab_report_submissions WHERE lab_report_submissions.Lab_Report_ID=$id"); + while ($row = mysqli_fetch_assoc($resultx1)) { + $count_subs = $row['cnt']; + } - Course Code - + $resultx2 = mysqli_query($con, "SELECT COUNT(*) AS cnt FROM lab_report_submissions WHERE lab_report_submissions.Lab_Report_ID=$id AND Marks IS NOT null"); + if (mysqli_num_rows($resultx2) == 0) { + $count_marked = 0; + } else { + while ($row = mysqli_fetch_assoc($resultx2)) { + $count_marked = $row['cnt']; + } + } - URL (Leave blank to use Course Code & Year) - + $header="Courses > ".$name."($code) > Assignments > ".$title; - Academic Year - + echo "
    +
    +
    $title ($marks Marks, $as_type)
    +
    $ins
    +

    Posted: $posted    Deadline: $deadline

    +

    $count_subs Submissions ( $count_marked Marked )

    + Edit + View + Extend Deadline +

    Attachments: $full_link

    +
    +
    "; - Faculty
    - - - - - - - Verify Joining Students - Yes - No - -
    -
    - -
    - - - -
    - - - - - - - -
    -
    -
    Course Portal > Students
    -' . $_SESSION['info_Courses_student'] . ''; - $_SESSION['info_Courses_student'] = null; - } - ?> -

    -
    -
    -
    - - - - - -
    -
    - - - - Search Results for Course Code $search
    "; - $result = mysqli_query($con,"SELECT `Course_ID`, `Course_Name`, `Academic_Year`, `Faculty`," - . " `Lecturer_User_ID`, `TA_User_ID`, `Course_Code`, `URL`, `Verify_New_Members` " - . " , users_table.Full_Name FROM `courses_table` INNER JOIN users_table" - . " ON users_table.User_ID=courses_table.Lecturer_User_ID where Academic_Year >= $oldest_academic_year and Course_Code like '%{$search}%' and courses_table.Course_ID not in (select course_id from course_students_table where Student_ID=$student_id) order by Academic_Year desc"); - } - // the user has entered something under "Find course by Code" - else - { - echo "

    Find Courses under faculty $faculty

    "; - $result = mysqli_query($con,"SELECT `Course_ID`, `Course_Name`, `Academic_Year`, `Faculty`, - `Lecturer_User_ID`, `TA_User_ID`, `Course_Code`, `URL`, `Verify_New_Members` - , users_table.Full_Name FROM `courses_table` INNER JOIN users_table - ON users_table.User_ID=courses_table.Lecturer_User_ID where Academic_Year >= $oldest_academic_year and Faculty='$faculty' and courses_table.Course_ID not in (select course_id from course_students_table where Student_ID=$student_id) order by Academic_Year desc"); - } - - - if(mysqli_num_rows($result)==0) - { - echo "No results found for your Search
    "; - - } else { - - while($row = mysqli_fetch_assoc($result)) { - $name=$row['Course_Name']; - $code=$row['Course_Code']; - $faculty=$row['Faculty']; - $lecturer=$row['Full_Name']; - $academic=$row['Academic_Year']; - $url=$row['URL']; - $id=$row['Course_ID']; - $v=$row['Verify_New_Members']; - $msg2="Join Course"; - if($v>0) - { - $msg=" Lecturer verification required"; - $msg2="Send Joining Request"; } - - echo "
    - [$code] $name
    ($url)
    $msg2 -
    Faculty: $faculty | Year: $academic | Lecturer: $lecturer
    $msg
    - "; } - } - } - // Otherwise, list the student's joined courses (already done), in reverse chronological order - echo "

    My Courses

    "; - $result = mysqli_query($con,"SELECT users_table.Full_Name, course_students_table.Status, courses_table.Course_ID, `Course_Name`, `Academic_Year`, `Faculty`, `Lecturer_User_ID`, `TA_User_ID`, `Course_Code`, `URL`, `Verify_New_Members` FROM `courses_table` -INNER JOIN users_table - ON users_table.User_ID=courses_table.Lecturer_User_ID + echo "
    "; -INNER JOIN course_students_table on course_students_table.Course_ID=courses_table.Course_ID + $resultx1 = mysqli_query($con, "SELECT DISTINCT course_students_table.Student_ID, users_table.Full_Name + FROM course_students_table + INNER JOIN users_table on users_table.Student_ID=course_students_table.Student_ID + WHERE Course_ID=$course_id"); - where course_students_table.Student_ID=$student_id order by Academic_Year desc"); - - if(mysqli_num_rows($result)==0) - { - echo " You are not Enrolled in any Course"; - } else { - while($row = mysqli_fetch_assoc($result)) { - $name=$row['Course_Name']; - $code=$row['Course_Code']; - $faculty=$row['Faculty']; - $lecturer=$row['Full_Name']; - $academic=$row['Academic_Year']; - $url=$row['URL']; - $id=$row['Course_ID']; - $Status=$row['Status']; - - if($Status=="Joined") - { - echo "
    - ($code) - $name
    ($url)     $Status     
    Open -
    Faculty : $faculty Year : $academic Lecturer :$lecturer
    - "; + echo ""; + return; + } + + ?> + + + +
    + + My courses"; + + $result = mysqli_query($con, "SELECT Course_ID, Course_Name, Academic_Year, Faculty, Lecturer_User_ID, TA_User_ID, Course_Code, URL, Verify_New_Members, users_table.Full_Name + FROM courses_table + INNER JOIN users_table ON users_table.User_ID=courses_table.Lecturer_User_ID + WHERE courses_table.Lecturer_User_ID=$user_id + ORDER BY Academic_Year DESC, URL ASC"); + + if ($_SESSION['user_type'] == "TA") { + $result = mysqli_query($con, "SELECT course_ta.Course_ID, Course_Name, Academic_Year, Faculty, Lecturer_User_ID, TA_User_ID, Course_Code, URL, Verify_New_Members + FROM courses_table + INNER JOIN course_ta ON course_ta.Course_ID=courses_table.Course_ID + WHERE course_ta.TA=$user_id"); + } + + if (mysqli_num_rows($result) != 0) { + while ($row = mysqli_fetch_assoc($result)) { + $id = $row['Course_ID']; + $name = $row['Course_Name']; + $code = $row['Course_Code']; + $faculty = $row['Faculty']; + $lecturer = $row['Full_Name']; + $academic = $row['Academic_Year']; + $url = $row['URL']; + $resultTA = mysqli_query($con, "SELECT Course_ID, TA, users_table.Full_Name AS TA_NAME + FROM course_ta + INNER JOIN users_table ON users_table.User_ID=course_ta.TA + WHERE course_ta.Course_ID=$id"); + $ta = ""; + + while ($rowTA = mysqli_fetch_assoc($resultTA)) { + $ta = $ta." ".$rowTA['TA_NAME']; + } + + if ($ta == "") { + $ta = " None"; + } + + + echo" +
    + ($code) - $name +

    Faculty: $faculty        Year: $academic        Lecturer: $lecturer        TA:$ta

    +
    +
    "; + } + } + ?> + +
    +
    +
    + Course joining requests + + No course-joining request so far for your courses
    "; + } else { + while ($row = mysqli_fetch_assoc($result)) { + $id = $row['ID']; + $name = $row['Course_Name']; + $code = $row['Course_Code']; + $faculty = $row['Faculty']; + $student_name = $row['Full_Name']; + $academic = $row['Academic_Year']; + echo "
    - ($code) - $name $Status -
    Faculty : $faculty Year : $academic Lecturer :$lecturer
    - "; + $student_name is Requesting to join
    [($code) - $name ]     
    + Accept    Decline +
    "; + } + } + ?> + + + Only Lecturer can post assignments"; + } + + if ($_SESSION['user_type'] == "Lecturer"){ + ?> + + Create a new course + +
    + + + Course name + + + Course code + + + URL (leave blank to use course code & year) + + + Academic year + + + Faculty
    + + + + + Verify joining students? + Yes + No + +

    +
    + +
    + + + +
    + + + + + + +
    +
    + + ' . $_SESSION['info_Courses_student'] . ''; + $_SESSION['info_Courses_student'] = null; + } + if (isset($_SESSION['info_signup'])) { + echo '
    '; + $_SESSION['info_signup'] = null; + } + ?> +

    +
    +
    +
    + + +
    +
    + + Search results for course code: $search
    "; + $result = mysqli_query($con, "SELECT Course_ID, Course_Name, Academic_Year, Faculty, Lecturer_User_ID, TA_User_ID, Course_Code, URL, Verify_New_Members, users_table.Full_Name + FROM courses_table + INNER JOIN users_table + ON users_table.User_ID=courses_table.Lecturer_User_ID + WHERE Academic_Year >= $oldest_academic_year AND Course_Code LIKE '%{$search}%' AND courses_table.Course_ID NOT IN + (SELECT course_id FROM course_students_table WHERE Student_ID=$student_id) ORDER BY Academic_Year DESC"); + } else if ($faculty != "") { // the user has entered something under "Find course by Code" + echo "

    Find courses under faculty: $faculty

    "; + $result = mysqli_query($con, "SELECT Course_ID, Course_Name, Academic_Year, Faculty, Lecturer_User_ID, TA_User_ID, Course_Code, URL, Verify_New_Members, users_table.Full_Name + FROM courses_table + INNER JOIN users_table ON users_table.User_ID=courses_table.Lecturer_User_ID + WHERE Academic_Year >= $oldest_academic_year AND Faculty='$faculty' AND courses_table.Course_ID NOT IN + (SELECT course_id FROM course_students_table WHERE Student_ID=$student_id) ORDER BY Academic_Year DESC"); + } + + if (mysqli_num_rows($result) == 0) { + echo "No such course offered in this academic year. Please check that your have entered the correct course code.
    "; + } else { + while($row = mysqli_fetch_assoc($result)) { + $name = $row['Course_Name']; + $code = $row['Course_Code']; + $faculty = $row['Faculty']; + $lecturer = $row['Full_Name']; + $academic = $row['Academic_Year']; + $url = $row['URL']; + $id = $row['Course_ID']; + $v = $row['Verify_New_Members']; + if($v > 0) { + $msg = " Lecturer verification required"; + $msg2 = "Send Joining Request"; + } + + echo "
    + ($code) $name
    ($url)
    + Join
    + Faculty: $faculty   Year: $academic   Lecturer: $lecturer
    + $msg +
    "; + } + } + } + // Otherwise, list the student's joined courses (already done), in reverse chronological order + echo "

    My courses

    "; + $result = mysqli_query($con, "SELECT users_table.Full_Name, course_students_table.Status, courses_table.Course_ID, Course_Name, Academic_Year, Faculty, Lecturer_User_ID, TA_User_ID, Course_Code, URL, Verify_New_Members + FROM courses_table + INNER JOIN users_table ON users_table.User_ID=courses_table.Lecturer_User_ID + INNER JOIN course_students_table ON course_students_table.Course_ID=courses_table.Course_ID + WHERE course_students_table.Student_ID=$student_id ORDER BY Academic_Year DESC, URL ASC"); + + if (mysqli_num_rows($result) == 0) { + echo " You are not enrolled in any Course"; + } else { + while($row = mysqli_fetch_assoc($result)) { + $name = $row['Course_Name']; + $code = $row['Course_Code']; + $faculty = $row['Faculty']; + $lecturer = $row['Full_Name']; + $academic_year = $row['Academic_Year']; + $url = $row['URL']; + $id = $row['Course_ID']; + $status = $row['Status']; + if($status == "Joined") { + echo " +
    + ($code) $name
    + ($url)     $status     
    + Faculty: $faculty   Year: $academic_year   Lecturer: $lecturer +
    +
    "; + } else { + echo "
    + ($code) $name $status
    + Faculty: $faculty   Year: $academic_year   Lecturer: $lecturer +
    "; + } + } + } + + echo "
    + +
    +
    +
    +
    +
    + Find new course by course code + +
    + +
    + List courses by faculty + -
    -List courses by faculty - +
    - } else { - echo""; - while($row = mysqli_fetch_assoc($result)) { - $fname=$row['Faculty']; - echo " "; - }} - - echo "
    - -
    -

    - +

    + +
    +
    +
    - -
    - +
    "; + } + ?> + -
    "; - +
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -} - -?> - - - - - - - - - - - - - - - + + diff --git a/Download.php b/Download.php index d59e90e..42db7d4 100644 --- a/Download.php +++ b/Download.php @@ -25,7 +25,7 @@ $type = filetype($file); $today = date("F j, Y, g:i a"); $time = time(); -if ((isset($_SESSION["user_student_id"]) && strpos($file, $_SESSION["user_student_id"]) > 0) || $_SESSION['user_type'] == "Lecturer" || $_SESSION['user_type'] == "TA" ) { +if ((isset($_SESSION["user_student_id"]) && (strpos($file, $_SESSION["user_student_id"]) > 0 || strpos($file, "Lab_Report_Assignments"))) || $_SESSION['user_type'] == "Lecturer" || $_SESSION['user_type'] == "TA" ) { // 发送文件头部 header("Content-type: $type"); header('Content-Disposition: attachment;filename="'.urldecode($filename).'"'); diff --git a/Footer.php b/Footer.php index de442d2..292966d 100644 --- a/Footer.php +++ b/Footer.php @@ -1,4 +1,3 @@ -








    -
    - -
    \ No newline at end of file +Copyright © 2018-" . date("Y") . " The Authors

    "; +?> diff --git a/Header.php b/Header.php index 63ad2ce..718a6b5 100644 --- a/Header.php +++ b/Header.php @@ -1,177 +1,205 @@ + + getMessage(); +} // Check database connection if (mysqli_connect_errno()) { - echo "Failed to connect to MySQL: " . mysqli_connect_error(); + echo " Error number: ".mysqli_connect_errno(); + exit(); } - ?> - + - + + + + LRR - - - - - + + - - - + + + + + - + - h1, - h2, - h3, - h4 { - color: #03407B; - } + - a { - color: #03407B; - } + - .break-word { - word-wrap: break-word; - white-space: -moz-pre-wrap !important; - /* Mozilla, since 1999 */ - white-space: -pre-wrap; - /* Opera 4-6 */ - white-space: -o-pre-wrap; - /* Opera 7 */ - white-space: pre-wrap; - /* css-3 */ - word-wrap: break-word; - /* Internet Explorer 5.5+ */ - white-space: -webkit-pre-wrap; - /* Newer versions of Chrome/Safari*/ - word-break: break-all; - white-space: normal; - } + + + + diff --git a/NoDirectPhpAcess.php b/NoDirectPhpAcess.php index 4e85779..3a3dafe 100644 --- a/NoDirectPhpAcess.php +++ b/NoDirectPhpAcess.php @@ -1,7 +1,7 @@ diff --git a/README.md b/README.md index b48e53a..e14051a 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ LRR (Lab Report Repository) is an online software application for course instructors to post, receive and mark assignments, and for students to submit assignments, or submit re-marking requests. This software was originally developed by Mahomed Nor in 2018, a graduate student in the Department of Computer Science at the Zhejiang Normal University, -while he was taking a graduate course called **Advanced Software Engineering** (http://lanlab.org/course/2018f/se/homepage.html). +while he was taking a graduate course called Advanced Software Engineering. -The LRR's project home page is at http://121.4.94.30/homepage/. For potential project contributors, we recommend that you browse its home page first to familiarize yourself with the project. +For potential project contributors, we recommend that you browse its home page at ./homepage/index.html first to familiarize yourself with the project. @@ -27,8 +27,8 @@ LRR needs Apache and MySQL to run. I followed [How To Install Linux, Apache, My LRR uses a database called `lrr`. So create this database using MySQL root account. Open MySQL's prompt using `sudo mysql`. Create the database using command `CREATE DATABASE lrr;`, and grant all privileges to MySQL user `lrr` using command `GRANT ALL PRIVILEGES ON lrr.* TO 'mnc'@'localhost' WITH GRANT OPTION;`. If MySQL user mnc does not exist, create it using command `CREATE USER 'mnc'@'localhost' IDENTIFIED BY 'password'`. To facilitate data migration, I need to export the existing `lrr` to a plain text file (including many sql commands) and import that text file to the newly created `lrr` database on the new server. -The command for exporting the database is `mysqldump -u mnc -p lrr > lrr_database_dump.txt`, where mnc after -u is MySQL's username, and lrr after -p is the database name. -The command for importing is `mysql -u mnc -p lrr < lrr_database_dump.txt`. Read [How to Import and Export MySQL Databases in Linux](https://phoenixnap.com/kb/import-and-export-mysql-database) for more detail. Do not have lrr_database_dump.txt? You can use lrr_database.sql in this repo instead. +The command for exporting the database is `mysqldump -u mnc -p lrr > lrr_database_dump.sql`, where mnc after -u is MySQL's username, and lrr after -p is the database name. +The command for importing is `mysql -u username -p lrr < lrr_database_dump.sql`. You must create database `lrr` first on your computer before doing the import. Read [How to Import and Export MySQL Databases in Linux](https://phoenixnap.com/kb/import-and-export-mysql-database) for more detail. Do not have lrr_database_dump.sql? You can use lrr_database.sql in this repo instead. LRR also needs to store assignment submissions. We store them in a folder called `../../lrr_submission`. Note that `lrr_submission` is two levels above the project folder (where many PHP files reside). I copied this folder from the existing one. I think it is also OK if you create an empty folder. We need to set a proper owner and accessibility for `lrr_submission` using the following two commands: @@ -51,6 +51,31 @@ Enable the site lrr: `sudo a2ensite lrr`. Restart the apache server: `sudo syst Visit the LRR application by entering this URL in a web browser: http://121.4.94.30/. +### Solving the coding problems in the dump file + +If the database contains Chinese characters, the dump file (e.g., lrr_database_dump.sql) may contain *weird* characters, e.g., `研究生`, so weird that no one can tell their meaning. + +We need to correct these abnormal characters before we import them to the new database, so that the PHP program can correctly display Chinese information. + +The simplest solution is using the ftfy (fixes text for you) Python package to convert them, as follows: + +``` +from ftfy import fix_text + +with open('lrr_database_dump.sql') as f: + content = f.read() + +content2 = fix_text(content) +with open('lrr_database_dump_sql_fixed.txt', 'w') as f: + f.write(content2) +``` + +Now, import data using lrr_database_dump_sql_*fixed*.txt. + +If you encounter the 'Unknown MySQL server host' problem during import, replace all apostrophes with a space in the dump file. For example, if a database table field contains *can't*, then the apostrophe between *n* and *t* can cause that problem. + + + ## Enock steps Enock, a graduate student here, has made a tutorial about how he deployed LRR to a remote server (http://lanlab.org/course/2021s/spm/PuTTY-Server.txt). @@ -134,7 +159,16 @@ https://github.com/spm2020spring/TeamCollaborationTutorial/blob/master/team.rst ## Testing -Make sure your changes can pass all the tests in folder [./test](http://121.4.94.30:3000/mrlan/LRR/src/branch/master/test). +Make sure your changes can pass all the tests in folder ./test. + +You cannot do too much unit testing for LRR because it almost does not +have functions or classes. However, you can do end-to-end testing. +It is important that you *restore* the database each time before your +run a test case. The fixture *restore_database* in ./test/conftest.py +is used to restore the database. Please check that. A use case for +this fixture can be found in the test script +./test/SeleniumMpiana/test_bug418_yaaqob.py. You could run this test script +by typing the following command: `pytest ./SeleniumMpiana/test_bug418_yaaqob.py` ## Communications Method @@ -148,10 +182,10 @@ We can also communicate through pull requests. You make a pull request, I revie ## Frequently Asked Questions -1. Q: The web application's front page does not show properly, i.e., elements are not well aligned. +1. Q: The web application's front page does not show properly, i.e., elements are not well aligned. A: You missed two folders `css` and `font-awesome`. These folders include third-party js or css files and therefore are not included. -1. Q: What if I do not have any information about the `lrr` database? +1. Q: What if I do not have any information about the `lrr` database? A: You could use `lrr_database.sql` to make a new database. @@ -224,6 +258,6 @@ Nicole-Rutagengwa - Nicole Rutagengwa - 2019169 # References -- 詹沈晨. (2020). [网页程序测试自动化 (Selenium) 测试效率](http://lanlab.org/ZhanShenchen-On-Automated-Web-Application-Test-Efficiency-with-Selenium.doc) +- 詹沈晨. (2020). 网页程序测试自动化 (Selenium) 测试效率. -- Ibrahim. (2021). [Defect analysis for LRR](http://lanlab.org/thesis/Defect-Analysis-for-LRR.docx) +- Ibrahim. (2021). Defect analysis for LRR] diff --git a/RecoverPassword.php b/RecoverPassword.php new file mode 100644 index 0000000..373eaba --- /dev/null +++ b/RecoverPassword.php @@ -0,0 +1,33 @@ + + + + +


    + +
    +
    +
    +
    +
    + Recover password + + Email +
    + +
    +
    +
    + + '; + $_SESSION['info_recover_password'] = null; + } + ?> + +
    + diff --git a/ResetPassword.php b/ResetPassword.php new file mode 100644 index 0000000..aa17765 --- /dev/null +++ b/ResetPassword.php @@ -0,0 +1,78 @@ +Password must be at least 8 characters long and include uppercase and lowercase letters, numbers, and special characters.'; + } elseif ($new_password !== $confirm_password) { + echo '
    Passwords do not match. Please try again.
    '; + } else { + $hashed_password = password_hash($new_password, PASSWORD_ARGON2ID); + $user_id = $_SESSION['user_id']; + + $stmt = $con->prepare("UPDATE users_table SET Password = ? WHERE email = ? AND user_id = ?"); + $stmt->bind_param("sss", $hashed_password, $email, $user_id); + + if ($stmt->execute()) { + echo '
    Password reset successfully. You can now log in with your new password.
    '; + unset($_SESSION['user_id']); // Clear user_id after successful password reset + header("Location: index.php"); + } else { + error_log("Error updating password for user ID: $user_id"); + echo '
    An error occurred. Please try again later.
    '; + } + $stmt->close(); + } + } +} + +// Display the reset password form +echo ' +


    +
    +
    +
    +
    +
    + Reset Your Password
    + New Password + +
    + Confirm New Password + +
    + +
    +
    +").appendTo(a)),o.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",o.opacity)),o.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",o.zIndex)),this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",t,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger("activate",t,this._uiHash(this)); -return e.ui.ddmanager&&(e.ui.ddmanager.current=this),e.ui.ddmanager&&!o.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(t),!0},_mouseDrag:function(t){var i,s,n,a,o=this.options,r=!1;for(this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-t.pageY=0;i--)if(s=this.items[i],n=s.item[0],a=this._intersectsWithPointer(s),a&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===a?"next":"prev"]()[0]!==n&&!e.contains(this.placeholder[0],n)&&("semi-dynamic"===this.options.type?!e.contains(this.element[0],n):!0)){if(this.direction=1===a?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(t,s),this._trigger("change",t,this._uiHash());break}return this._contactContainers(t),e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),this._trigger("sort",t,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(t,i){if(t){if(e.ui.ddmanager&&!this.options.dropBehaviour&&e.ui.ddmanager.drop(this,t),this.options.revert){var s=this,n=this.placeholder.offset(),a=this.options.axis,o={};a&&"x"!==a||(o.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollLeft)),a&&"y"!==a||(o.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,e(this.helper).animate(o,parseInt(this.options.revert,10)||500,function(){s._clear(t)})}else this._clear(t,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp({target:null}),"original"===this.options.helper?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var t=this.containers.length-1;t>=0;t--)this.containers[t]._trigger("deactivate",null,this._uiHash(this)),this.containers[t].containerCache.over&&(this.containers[t]._trigger("out",null,this._uiHash(this)),this.containers[t].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),e.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?e(this.domPosition.prev).after(this.currentItem):e(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(t){var i=this._getItemsAsjQuery(t&&t.connected),s=[];return t=t||{},e(i).each(function(){var i=(e(t.item||this).attr(t.attribute||"id")||"").match(t.expression||/(.+)[\-=_](.+)/);i&&s.push((t.key||i[1]+"[]")+"="+(t.key&&t.expression?i[1]:i[2]))}),!s.length&&t.key&&s.push(t.key+"="),s.join("&")},toArray:function(t){var i=this._getItemsAsjQuery(t&&t.connected),s=[];return t=t||{},i.each(function(){s.push(e(t.item||this).attr(t.attribute||"id")||"")}),s},_intersectsWith:function(e){var t=this.positionAbs.left,i=t+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,a=e.left,o=a+e.width,r=e.top,h=r+e.height,l=this.offset.click.top,u=this.offset.click.left,d="x"===this.options.axis||s+l>r&&h>s+l,c="y"===this.options.axis||t+u>a&&o>t+u,p=d&&c;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>e[this.floating?"width":"height"]?p:t+this.helperProportions.width/2>a&&o>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&h>n-this.helperProportions.height/2},_intersectsWithPointer:function(e){var t="x"===this.options.axis||this._isOverAxis(this.positionAbs.top+this.offset.click.top,e.top,e.height),i="y"===this.options.axis||this._isOverAxis(this.positionAbs.left+this.offset.click.left,e.left,e.width),s=t&&i,n=this._getDragVerticalDirection(),a=this._getDragHorizontalDirection();return s?this.floating?a&&"right"===a||"down"===n?2:1:n&&("down"===n?2:1):!1},_intersectsWithSides:function(e){var t=this._isOverAxis(this.positionAbs.top+this.offset.click.top,e.top+e.height/2,e.height),i=this._isOverAxis(this.positionAbs.left+this.offset.click.left,e.left+e.width/2,e.width),s=this._getDragVerticalDirection(),n=this._getDragHorizontalDirection();return this.floating&&n?"right"===n&&i||"left"===n&&!i:s&&("down"===s&&t||"up"===s&&!t)},_getDragVerticalDirection:function(){var e=this.positionAbs.top-this.lastPositionAbs.top;return 0!==e&&(e>0?"down":"up")},_getDragHorizontalDirection:function(){var e=this.positionAbs.left-this.lastPositionAbs.left;return 0!==e&&(e>0?"right":"left")},refresh:function(e){return this._refreshItems(e),this._setHandleClassName(),this.refreshPositions(),this},_connectWith:function(){var e=this.options;return e.connectWith.constructor===String?[e.connectWith]:e.connectWith},_getItemsAsjQuery:function(t){function i(){r.push(this)}var s,n,a,o,r=[],h=[],l=this._connectWith();if(l&&t)for(s=l.length-1;s>=0;s--)for(a=e(l[s]),n=a.length-1;n>=0;n--)o=e.data(a[n],this.widgetFullName),o&&o!==this&&!o.options.disabled&&h.push([e.isFunction(o.options.items)?o.options.items.call(o.element):e(o.options.items,o.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),o]);for(h.push([e.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):e(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),s=h.length-1;s>=0;s--)h[s][0].each(i);return e(r)},_removeCurrentsFromItems:function(){var t=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=e.grep(this.items,function(e){for(var i=0;t.length>i;i++)if(t[i]===e.item[0])return!1;return!0})},_refreshItems:function(t){this.items=[],this.containers=[this];var i,s,n,a,o,r,h,l,u=this.items,d=[[e.isFunction(this.options.items)?this.options.items.call(this.element[0],t,{item:this.currentItem}):e(this.options.items,this.element),this]],c=this._connectWith();if(c&&this.ready)for(i=c.length-1;i>=0;i--)for(n=e(c[i]),s=n.length-1;s>=0;s--)a=e.data(n[s],this.widgetFullName),a&&a!==this&&!a.options.disabled&&(d.push([e.isFunction(a.options.items)?a.options.items.call(a.element[0],t,{item:this.currentItem}):e(a.options.items,a.element),a]),this.containers.push(a));for(i=d.length-1;i>=0;i--)for(o=d[i][1],r=d[i][0],s=0,l=r.length;l>s;s++)h=e(r[s]),h.data(this.widgetName+"-item",o),u.push({item:h,instance:o,width:0,height:0,left:0,top:0})},refreshPositions:function(t){this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,a;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?e(this.options.toleranceElement,s.item):s.item,t||(s.width=n.outerWidth(),s.height=n.outerHeight()),a=n.offset(),s.left=a.left,s.top=a.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)a=this.containers[i].element.offset(),this.containers[i].containerCache.left=a.left,this.containers[i].containerCache.top=a.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(t){t=t||this;var i,s=t.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=t.currentItem[0].nodeName.toLowerCase(),n=e("<"+s+">",t.document[0]).addClass(i||t.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper");return"tr"===s?t.currentItem.children().each(function(){e(" ",t.document[0]).attr("colspan",e(this).attr("colspan")||1).appendTo(n)}):"img"===s&&n.attr("src",t.currentItem.attr("src")),i||n.css("visibility","hidden"),n},update:function(e,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(t.currentItem.innerHeight()-parseInt(t.currentItem.css("paddingTop")||0,10)-parseInt(t.currentItem.css("paddingBottom")||0,10)),n.width()||n.width(t.currentItem.innerWidth()-parseInt(t.currentItem.css("paddingLeft")||0,10)-parseInt(t.currentItem.css("paddingRight")||0,10)))}}),t.placeholder=e(s.placeholder.element.call(t.element,t.currentItem)),t.currentItem.after(t.placeholder),s.placeholder.update(t,t.placeholder)},_contactContainers:function(t){var i,s,n,a,o,r,h,l,u,d,c=null,p=null;for(i=this.containers.length-1;i>=0;i--)if(!e.contains(this.currentItem[0],this.containers[i].element[0]))if(this._intersectsWith(this.containers[i].containerCache)){if(c&&e.contains(this.containers[i].element[0],c.element[0]))continue;c=this.containers[i],p=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",t,this._uiHash(this)),this.containers[i].containerCache.over=0);if(c)if(1===this.containers.length)this.containers[p].containerCache.over||(this.containers[p]._trigger("over",t,this._uiHash(this)),this.containers[p].containerCache.over=1);else{for(n=1e4,a=null,u=c.floating||this._isFloating(this.currentItem),o=u?"left":"top",r=u?"width":"height",d=u?"clientX":"clientY",s=this.items.length-1;s>=0;s--)e.contains(this.containers[p].element[0],this.items[s].item[0])&&this.items[s].item[0]!==this.currentItem[0]&&(h=this.items[s].item.offset()[o],l=!1,t[d]-h>this.items[s][r]/2&&(l=!0),n>Math.abs(t[d]-h)&&(n=Math.abs(t[d]-h),a=this.items[s],this.direction=l?"up":"down"));if(!a&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[p])return;a?this._rearrange(t,a,null,!0):this._rearrange(t,null,this.containers[p].element,!0),this._trigger("change",t,this._uiHash()),this.containers[p]._trigger("change",t,this._uiHash(this)),this.currentContainer=this.containers[p],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[p]._trigger("over",t,this._uiHash(this)),this.containers[p].containerCache.over=1}},_createHelper:function(t){var i=this.options,s=e.isFunction(i.helper)?e(i.helper.apply(this.element[0],[t,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||e("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(t){"string"==typeof t&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var t=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==document&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&e.ui.ie)&&(t={top:0,left:0}),{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var e=this.currentItem.position();return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:e.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t,i,s,n=this.options;"parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,e("document"===n.containment?document:window).width()-this.helperProportions.width-this.margins.left,(e("document"===n.containment?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(t=e(n.containment)[0],i=e(n.containment).offset(),s="hidden"!==e(t).css("overflow"),this.containment=[i.left+(parseInt(e(t).css("borderLeftWidth"),10)||0)+(parseInt(e(t).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(e(t).css("borderTopWidth"),10)||0)+(parseInt(e(t).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(t.scrollWidth,t.offsetWidth):t.offsetWidth)-(parseInt(e(t).css("borderLeftWidth"),10)||0)-(parseInt(e(t).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(t.scrollHeight,t.offsetHeight):t.offsetHeight)-(parseInt(e(t).css("borderTopWidth"),10)||0)-(parseInt(e(t).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(t,i){i||(i=this.position);var s="absolute"===t?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,a=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():a?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():a?0:n.scrollLeft())*s}},_generatePosition:function(t){var i,s,n=this.options,a=t.pageX,o=t.pageY,r="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(r[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==document&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(t.pageX-this.offset.click.leftthis.containment[2]&&(a=this.containment[2]+this.offset.click.left),t.pageY-this.offset.click.top>this.containment[3]&&(o=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((o-this.originalPageY)/n.grid[1])*n.grid[1],o=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((a-this.originalPageX)/n.grid[0])*n.grid[0],a=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:o-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():h?0:r.scrollTop()),left:a-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:r.scrollLeft())}},_rearrange:function(e,t,i,s){i?i[0].appendChild(this.placeholder[0]):t.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?t.item[0]:t.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(e,t){function i(e,t,i){return function(s){i._trigger(e,s,t._uiHash(t))}}this.reverting=!1;var s,n=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(s in this._storedCSS)("auto"===this._storedCSS[s]||"static"===this._storedCSS[s])&&(this._storedCSS[s]="");this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!t&&n.push(function(e){this._trigger("receive",e,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||t||n.push(function(e){this._trigger("update",e,this._uiHash())}),this!==this.currentContainer&&(t||(n.push(function(e){this._trigger("remove",e,this._uiHash())}),n.push(function(e){return function(t){e._trigger("receive",t,this._uiHash(this))}}.call(this,this.currentContainer)),n.push(function(e){return function(t){e._trigger("update",t,this._uiHash(this))}}.call(this,this.currentContainer)))),s=this.containers.length-1;s>=0;s--)t||n.push(i("deactivate",this,this.containers[s])),this.containers[s].containerCache.over&&(n.push(i("out",this,this.containers[s])),this.containers[s].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,this.cancelHelperRemoval){if(!t){for(this._trigger("beforeStop",e,this._uiHash()),s=0;n.length>s;s++)n[s].call(this,e);this._trigger("stop",e,this._uiHash())}return this.fromOutside=!1,!1}if(t||this._trigger("beforeStop",e,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null,!t){for(s=0;n.length>s;s++)n[s].call(this,e);this._trigger("stop",e,this._uiHash())}return this.fromOutside=!1,!0},_trigger:function(){e.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(t){var i=t||this;return{helper:i.helper,placeholder:i.placeholder||e([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:t?t.element:null}}}),e.widget("ui.spinner",{version:"1.11.1",defaultElement:"",widgetEventPrefix:"spin",options:{culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),""!==this.value()&&this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var t={},i=this.element;return e.each(["min","max","step"],function(e,s){var n=i.attr(s);void 0!==n&&n.length&&(t[s]=n)}),t},_events:{keydown:function(e){this._start(e)&&this._keydown(e)&&e.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(e){return this.cancelBlur?(delete this.cancelBlur,void 0):(this._stop(),this._refresh(),this.previous!==this.element.val()&&this._trigger("change",e),void 0)},mousewheel:function(e,t){if(t){if(!this.spinning&&!this._start(e))return!1;this._spin((t>0?1:-1)*this.options.step,e),clearTimeout(this.mousewheelTimer),this.mousewheelTimer=this._delay(function(){this.spinning&&this._stop(e)},100),e.preventDefault()}},"mousedown .ui-spinner-button":function(t){function i(){var e=this.element[0]===this.document[0].activeElement;e||(this.element.focus(),this.previous=s,this._delay(function(){this.previous=s}))}var s;s=this.element[0]===this.document[0].activeElement?this.previous:this.element.val(),t.preventDefault(),i.call(this),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,i.call(this)}),this._start(t)!==!1&&this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t)},"mouseup .ui-spinner-button":"_stop","mouseenter .ui-spinner-button":function(t){return e(t.currentTarget).hasClass("ui-state-active")?this._start(t)===!1?!1:(this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t),void 0):void 0},"mouseleave .ui-spinner-button":"_stop"},_draw:function(){var e=this.uiSpinner=this.element.addClass("ui-spinner-input").attr("autocomplete","off").wrap(this._uiSpinnerHtml()).parent().append(this._buttonHtml());this.element.attr("role","spinbutton"),this.buttons=e.find(".ui-spinner-button").attr("tabIndex",-1).button().removeClass("ui-corner-all"),this.buttons.height()>Math.ceil(.5*e.height())&&e.height()>0&&e.height(e.height()),this.options.disabled&&this.disable()},_keydown:function(t){var i=this.options,s=e.ui.keyCode;switch(t.keyCode){case s.UP:return this._repeat(null,1,t),!0;case s.DOWN:return this._repeat(null,-1,t),!0;case s.PAGE_UP:return this._repeat(null,i.page,t),!0;case s.PAGE_DOWN:return this._repeat(null,-i.page,t),!0}return!1},_uiSpinnerHtml:function(){return""},_buttonHtml:function(){return""+""+""+""+""},_start:function(e){return this.spinning||this._trigger("start",e)!==!1?(this.counter||(this.counter=1),this.spinning=!0,!0):!1},_repeat:function(e,t,i){e=e||500,clearTimeout(this.timer),this.timer=this._delay(function(){this._repeat(40,t,i)},e),this._spin(t*this.options.step,i)},_spin:function(e,t){var i=this.value()||0;this.counter||(this.counter=1),i=this._adjustValue(i+e*this._increment(this.counter)),this.spinning&&this._trigger("spin",t,{value:i})===!1||(this._value(i),this.counter++)},_increment:function(t){var i=this.options.incremental;return i?e.isFunction(i)?i(t):Math.floor(t*t*t/5e4-t*t/500+17*t/200+1):1},_precision:function(){var e=this._precisionOf(this.options.step);return null!==this.options.min&&(e=Math.max(e,this._precisionOf(this.options.min))),e},_precisionOf:function(e){var t=""+e,i=t.indexOf(".");return-1===i?0:t.length-i-1},_adjustValue:function(e){var t,i,s=this.options;return t=null!==s.min?s.min:0,i=e-t,i=Math.round(i/s.step)*s.step,e=t+i,e=parseFloat(e.toFixed(this._precision())),null!==s.max&&e>s.max?s.max:null!==s.min&&s.min>e?s.min:e},_stop:function(e){this.spinning&&(clearTimeout(this.timer),clearTimeout(this.mousewheelTimer),this.counter=0,this.spinning=!1,this._trigger("stop",e))},_setOption:function(e,t){if("culture"===e||"numberFormat"===e){var i=this._parse(this.element.val());return this.options[e]=t,this.element.val(this._format(i)),void 0}("max"===e||"min"===e||"step"===e)&&"string"==typeof t&&(t=this._parse(t)),"icons"===e&&(this.buttons.first().find(".ui-icon").removeClass(this.options.icons.up).addClass(t.up),this.buttons.last().find(".ui-icon").removeClass(this.options.icons.down).addClass(t.down)),this._super(e,t),"disabled"===e&&(this.widget().toggleClass("ui-state-disabled",!!t),this.element.prop("disabled",!!t),this.buttons.button(t?"disable":"enable"))},_setOptions:h(function(e){this._super(e)}),_parse:function(e){return"string"==typeof e&&""!==e&&(e=window.Globalize&&this.options.numberFormat?Globalize.parseFloat(e,10,this.options.culture):+e),""===e||isNaN(e)?null:e},_format:function(e){return""===e?"":window.Globalize&&this.options.numberFormat?Globalize.format(e,this.options.numberFormat,this.options.culture):e},_refresh:function(){this.element.attr({"aria-valuemin":this.options.min,"aria-valuemax":this.options.max,"aria-valuenow":this._parse(this.element.val())})},isValid:function(){var e=this.value();return null===e?!1:e===this._adjustValue(e)},_value:function(e,t){var i;""!==e&&(i=this._parse(e),null!==i&&(t||(i=this._adjustValue(i)),e=this._format(i))),this.element.val(e),this._refresh()},_destroy:function(){this.element.removeClass("ui-spinner-input").prop("disabled",!1).removeAttr("autocomplete").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.uiSpinner.replaceWith(this.element)},stepUp:h(function(e){this._stepUp(e)}),_stepUp:function(e){this._start()&&(this._spin((e||1)*this.options.step),this._stop())},stepDown:h(function(e){this._stepDown(e)}),_stepDown:function(e){this._start()&&(this._spin((e||1)*-this.options.step),this._stop())},pageUp:h(function(e){this._stepUp((e||1)*this.options.page)}),pageDown:h(function(e){this._stepDown((e||1)*this.options.page)}),value:function(e){return arguments.length?(h(this._value).call(this,e),void 0):this._parse(this.element.val())},widget:function(){return this.uiSpinner}}),e.widget("ui.tabs",{version:"1.11.1",delay:300,options:{active:null,collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_isLocal:function(){var e=/#.*$/;return function(t){var i,s;t=t.cloneNode(!1),i=t.href.replace(e,""),s=location.href.replace(e,"");try{i=decodeURIComponent(i)}catch(n){}try{s=decodeURIComponent(s)}catch(n){}return t.hash.length>1&&i===s}}(),_create:function(){var t=this,i=this.options;this.running=!1,this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all").toggleClass("ui-tabs-collapsible",i.collapsible),this._processTabs(),i.active=this._initialActive(),e.isArray(i.disabled)&&(i.disabled=e.unique(i.disabled.concat(e.map(this.tabs.filter(".ui-state-disabled"),function(e){return t.tabs.index(e)}))).sort()),this.active=this.options.active!==!1&&this.anchors.length?this._findActive(i.active):e(),this._refresh(),this.active.length&&this.load(i.active)},_initialActive:function(){var t=this.options.active,i=this.options.collapsible,s=location.hash.substring(1);return null===t&&(s&&this.tabs.each(function(i,n){return e(n).attr("aria-controls")===s?(t=i,!1):void 0}),null===t&&(t=this.tabs.index(this.tabs.filter(".ui-tabs-active"))),(null===t||-1===t)&&(t=this.tabs.length?0:!1)),t!==!1&&(t=this.tabs.index(this.tabs.eq(t)),-1===t&&(t=i?!1:0)),!i&&t===!1&&this.anchors.length&&(t=0),t},_getCreateEventData:function(){return{tab:this.active,panel:this.active.length?this._getPanelForTab(this.active):e()}},_tabKeydown:function(t){var i=e(this.document[0].activeElement).closest("li"),s=this.tabs.index(i),n=!0;if(!this._handlePageNav(t)){switch(t.keyCode){case e.ui.keyCode.RIGHT:case e.ui.keyCode.DOWN:s++;break;case e.ui.keyCode.UP:case e.ui.keyCode.LEFT:n=!1,s--;break;case e.ui.keyCode.END:s=this.anchors.length-1;break;case e.ui.keyCode.HOME:s=0;break;case e.ui.keyCode.SPACE:return t.preventDefault(),clearTimeout(this.activating),this._activate(s),void 0;case e.ui.keyCode.ENTER:return t.preventDefault(),clearTimeout(this.activating),this._activate(s===this.options.active?!1:s),void 0;default:return}t.preventDefault(),clearTimeout(this.activating),s=this._focusNextTab(s,n),t.ctrlKey||(i.attr("aria-selected","false"),this.tabs.eq(s).attr("aria-selected","true"),this.activating=this._delay(function(){this.option("active",s)},this.delay))}},_panelKeydown:function(t){this._handlePageNav(t)||t.ctrlKey&&t.keyCode===e.ui.keyCode.UP&&(t.preventDefault(),this.active.focus())},_handlePageNav:function(t){return t.altKey&&t.keyCode===e.ui.keyCode.PAGE_UP?(this._activate(this._focusNextTab(this.options.active-1,!1)),!0):t.altKey&&t.keyCode===e.ui.keyCode.PAGE_DOWN?(this._activate(this._focusNextTab(this.options.active+1,!0)),!0):void 0},_findNextTab:function(t,i){function s(){return t>n&&(t=0),0>t&&(t=n),t}for(var n=this.tabs.length-1;-1!==e.inArray(s(),this.options.disabled);)t=i?t+1:t-1;return t},_focusNextTab:function(e,t){return e=this._findNextTab(e,t),this.tabs.eq(e).focus(),e},_setOption:function(e,t){return"active"===e?(this._activate(t),void 0):"disabled"===e?(this._setupDisabled(t),void 0):(this._super(e,t),"collapsible"===e&&(this.element.toggleClass("ui-tabs-collapsible",t),t||this.options.active!==!1||this._activate(0)),"event"===e&&this._setupEvents(t),"heightStyle"===e&&this._setupHeightStyle(t),void 0)},_sanitizeSelector:function(e){return e?e.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var t=this.options,i=this.tablist.children(":has(a[href])");t.disabled=e.map(i.filter(".ui-state-disabled"),function(e){return i.index(e)}),this._processTabs(),t.active!==!1&&this.anchors.length?this.active.length&&!e.contains(this.tablist[0],this.active[0])?this.tabs.length===t.disabled.length?(t.active=!1,this.active=e()):this._activate(this._findNextTab(Math.max(0,t.active-1),!1)):t.active=this.tabs.index(this.active):(t.active=!1,this.active=e()),this._refresh()},_refresh:function(){this._setupDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-hidden":"true"}),this.active.length?(this.active.addClass("ui-tabs-active ui-state-active").attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}),this._getPanelForTab(this.active).show().attr({"aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var t=this;this.tablist=this._getList().addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").attr("role","tablist").delegate("> li","mousedown"+this.eventNamespace,function(t){e(this).is(".ui-state-disabled")&&t.preventDefault()}).delegate(".ui-tabs-anchor","focus"+this.eventNamespace,function(){e(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this.tabs=this.tablist.find("> li:has(a[href])").addClass("ui-state-default ui-corner-top").attr({role:"tab",tabIndex:-1}),this.anchors=this.tabs.map(function(){return e("a",this)[0] -}).addClass("ui-tabs-anchor").attr({role:"presentation",tabIndex:-1}),this.panels=e(),this.anchors.each(function(i,s){var n,a,o,r=e(s).uniqueId().attr("id"),h=e(s).closest("li"),l=h.attr("aria-controls");t._isLocal(s)?(n=s.hash,o=n.substring(1),a=t.element.find(t._sanitizeSelector(n))):(o=h.attr("aria-controls")||e({}).uniqueId()[0].id,n="#"+o,a=t.element.find(n),a.length||(a=t._createPanel(o),a.insertAfter(t.panels[i-1]||t.tablist)),a.attr("aria-live","polite")),a.length&&(t.panels=t.panels.add(a)),l&&h.data("ui-tabs-aria-controls",l),h.attr({"aria-controls":o,"aria-labelledby":r}),a.attr("aria-labelledby",r)}),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").attr("role","tabpanel")},_getList:function(){return this.tablist||this.element.find("ol,ul").eq(0)},_createPanel:function(t){return e("
    ").attr("id",t).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)},_setupDisabled:function(t){e.isArray(t)&&(t.length?t.length===this.anchors.length&&(t=!0):t=!1);for(var i,s=0;i=this.tabs[s];s++)t===!0||-1!==e.inArray(s,t)?e(i).addClass("ui-state-disabled").attr("aria-disabled","true"):e(i).removeClass("ui-state-disabled").removeAttr("aria-disabled");this.options.disabled=t},_setupEvents:function(t){var i={};t&&e.each(t.split(" "),function(e,t){i[t]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(!0,this.anchors,{click:function(e){e.preventDefault()}}),this._on(this.anchors,i),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(t){var i,s=this.element.parent();"fill"===t?(i=s.height(),i-=this.element.outerHeight()-this.element.height(),this.element.siblings(":visible").each(function(){var t=e(this),s=t.css("position");"absolute"!==s&&"fixed"!==s&&(i-=t.outerHeight(!0))}),this.element.children().not(this.panels).each(function(){i-=e(this).outerHeight(!0)}),this.panels.each(function(){e(this).height(Math.max(0,i-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):"auto"===t&&(i=0,this.panels.each(function(){i=Math.max(i,e(this).height("").height())}).height(i))},_eventHandler:function(t){var i=this.options,s=this.active,n=e(t.currentTarget),a=n.closest("li"),o=a[0]===s[0],r=o&&i.collapsible,h=r?e():this._getPanelForTab(a),l=s.length?this._getPanelForTab(s):e(),u={oldTab:s,oldPanel:l,newTab:r?e():a,newPanel:h};t.preventDefault(),a.hasClass("ui-state-disabled")||a.hasClass("ui-tabs-loading")||this.running||o&&!i.collapsible||this._trigger("beforeActivate",t,u)===!1||(i.active=r?!1:this.tabs.index(a),this.active=o?e():a,this.xhr&&this.xhr.abort(),l.length||h.length||e.error("jQuery UI Tabs: Mismatching fragment identifier."),h.length&&this.load(this.tabs.index(a),t),this._toggle(t,u))},_toggle:function(t,i){function s(){a.running=!1,a._trigger("activate",t,i)}function n(){i.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),o.length&&a.options.show?a._show(o,a.options.show,s):(o.show(),s())}var a=this,o=i.newPanel,r=i.oldPanel;this.running=!0,r.length&&this.options.hide?this._hide(r,this.options.hide,function(){i.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),n()}):(i.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),r.hide(),n()),r.attr("aria-hidden","true"),i.oldTab.attr({"aria-selected":"false","aria-expanded":"false"}),o.length&&r.length?i.oldTab.attr("tabIndex",-1):o.length&&this.tabs.filter(function(){return 0===e(this).attr("tabIndex")}).attr("tabIndex",-1),o.attr("aria-hidden","false"),i.newTab.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_activate:function(t){var i,s=this._findActive(t);s[0]!==this.active[0]&&(s.length||(s=this.active),i=s.find(".ui-tabs-anchor")[0],this._eventHandler({target:i,currentTarget:i,preventDefault:e.noop}))},_findActive:function(t){return t===!1?e():this.tabs.eq(t)},_getIndex:function(e){return"string"==typeof e&&(e=this.anchors.index(this.anchors.filter("[href$='"+e+"']"))),e},_destroy:function(){this.xhr&&this.xhr.abort(),this.element.removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible"),this.tablist.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").removeAttr("role"),this.anchors.removeClass("ui-tabs-anchor").removeAttr("role").removeAttr("tabIndex").removeUniqueId(),this.tablist.unbind(this.eventNamespace),this.tabs.add(this.panels).each(function(){e.data(this,"ui-tabs-destroy")?e(this).remove():e(this).removeClass("ui-state-default ui-state-active ui-state-disabled ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel").removeAttr("tabIndex").removeAttr("aria-live").removeAttr("aria-busy").removeAttr("aria-selected").removeAttr("aria-labelledby").removeAttr("aria-hidden").removeAttr("aria-expanded").removeAttr("role")}),this.tabs.each(function(){var t=e(this),i=t.data("ui-tabs-aria-controls");i?t.attr("aria-controls",i).removeData("ui-tabs-aria-controls"):t.removeAttr("aria-controls")}),this.panels.show(),"content"!==this.options.heightStyle&&this.panels.css("height","")},enable:function(t){var i=this.options.disabled;i!==!1&&(void 0===t?i=!1:(t=this._getIndex(t),i=e.isArray(i)?e.map(i,function(e){return e!==t?e:null}):e.map(this.tabs,function(e,i){return i!==t?i:null})),this._setupDisabled(i))},disable:function(t){var i=this.options.disabled;if(i!==!0){if(void 0===t)i=!0;else{if(t=this._getIndex(t),-1!==e.inArray(t,i))return;i=e.isArray(i)?e.merge([t],i).sort():[t]}this._setupDisabled(i)}},load:function(t,i){t=this._getIndex(t);var s=this,n=this.tabs.eq(t),a=n.find(".ui-tabs-anchor"),o=this._getPanelForTab(n),r={tab:n,panel:o};this._isLocal(a[0])||(this.xhr=e.ajax(this._ajaxSettings(a,i,r)),this.xhr&&"canceled"!==this.xhr.statusText&&(n.addClass("ui-tabs-loading"),o.attr("aria-busy","true"),this.xhr.success(function(e){setTimeout(function(){o.html(e),s._trigger("load",i,r)},1)}).complete(function(e,t){setTimeout(function(){"abort"===t&&s.panels.stop(!1,!0),n.removeClass("ui-tabs-loading"),o.removeAttr("aria-busy"),e===s.xhr&&delete s.xhr},1)})))},_ajaxSettings:function(t,i,s){var n=this;return{url:t.attr("href"),beforeSend:function(t,a){return n._trigger("beforeLoad",i,e.extend({jqXHR:t,ajaxSettings:a},s))}}},_getPanelForTab:function(t){var i=e(t).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+i))}}),e.widget("ui.tooltip",{version:"1.11.1",options:{content:function(){var t=e(this).attr("title")||"";return e("").text(t).html()},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,tooltipClass:null,track:!1,close:null,open:null},_addDescribedBy:function(t,i){var s=(t.attr("aria-describedby")||"").split(/\s+/);s.push(i),t.data("ui-tooltip-id",i).attr("aria-describedby",e.trim(s.join(" ")))},_removeDescribedBy:function(t){var i=t.data("ui-tooltip-id"),s=(t.attr("aria-describedby")||"").split(/\s+/),n=e.inArray(i,s);-1!==n&&s.splice(n,1),t.removeData("ui-tooltip-id"),s=e.trim(s.join(" ")),s?t.attr("aria-describedby",s):t.removeAttr("aria-describedby")},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.options.disabled&&this._disable(),this.liveRegion=e("
    ").attr({role:"log","aria-live":"assertive","aria-relevant":"additions"}).addClass("ui-helper-hidden-accessible").appendTo(this.document[0].body)},_setOption:function(t,i){var s=this;return"disabled"===t?(this[i?"_disable":"_enable"](),this.options[t]=i,void 0):(this._super(t,i),"content"===t&&e.each(this.tooltips,function(e,t){s._updateContent(t)}),void 0)},_disable:function(){var t=this;e.each(this.tooltips,function(i,s){var n=e.Event("blur");n.target=n.currentTarget=s[0],t.close(n,!0)}),this.element.find(this.options.items).addBack().each(function(){var t=e(this);t.is("[title]")&&t.data("ui-tooltip-title",t.attr("title")).removeAttr("title")})},_enable:function(){this.element.find(this.options.items).addBack().each(function(){var t=e(this);t.data("ui-tooltip-title")&&t.attr("title",t.data("ui-tooltip-title"))})},open:function(t){var i=this,s=e(t?t.target:this.element).closest(this.options.items);s.length&&!s.data("ui-tooltip-id")&&(s.attr("title")&&s.data("ui-tooltip-title",s.attr("title")),s.data("ui-tooltip-open",!0),t&&"mouseover"===t.type&&s.parents().each(function(){var t,s=e(this);s.data("ui-tooltip-open")&&(t=e.Event("blur"),t.target=t.currentTarget=this,i.close(t,!0)),s.attr("title")&&(s.uniqueId(),i.parents[this.id]={element:this,title:s.attr("title")},s.attr("title",""))}),this._updateContent(s,t))},_updateContent:function(e,t){var i,s=this.options.content,n=this,a=t?t.type:null;return"string"==typeof s?this._open(t,e,s):(i=s.call(e[0],function(i){e.data("ui-tooltip-open")&&n._delay(function(){t&&(t.type=a),this._open(t,e,i)})}),i&&this._open(t,e,i),void 0)},_open:function(t,i,s){function n(e){l.of=e,a.is(":hidden")||a.position(l)}var a,o,r,h,l=e.extend({},this.options.position);if(s){if(a=this._find(i),a.length)return a.find(".ui-tooltip-content").html(s),void 0;i.is("[title]")&&(t&&"mouseover"===t.type?i.attr("title",""):i.removeAttr("title")),a=this._tooltip(i),this._addDescribedBy(i,a.attr("id")),a.find(".ui-tooltip-content").html(s),this.liveRegion.children().hide(),s.clone?(h=s.clone(),h.removeAttr("id").find("[id]").removeAttr("id")):h=s,e("
    ").html(h).appendTo(this.liveRegion),this.options.track&&t&&/^mouse/.test(t.type)?(this._on(this.document,{mousemove:n}),n(t)):a.position(e.extend({of:i},this.options.position)),this.hiding=!1,this.closing=!1,a.hide(),this._show(a,this.options.show),this.options.show&&this.options.show.delay&&(r=this.delayedShow=setInterval(function(){a.is(":visible")&&(n(l.of),clearInterval(r))},e.fx.interval)),this._trigger("open",t,{tooltip:a}),o={keyup:function(t){if(t.keyCode===e.ui.keyCode.ESCAPE){var s=e.Event(t);s.currentTarget=i[0],this.close(s,!0)}}},i[0]!==this.element[0]&&(o.remove=function(){this._removeTooltip(a)}),t&&"mouseover"!==t.type||(o.mouseleave="close"),t&&"focusin"!==t.type||(o.focusout="close"),this._on(!0,i,o)}},close:function(t){var i=this,s=e(t?t.currentTarget:this.element),n=this._find(s);this.closing||(clearInterval(this.delayedShow),s.data("ui-tooltip-title")&&!s.attr("title")&&s.attr("title",s.data("ui-tooltip-title")),this._removeDescribedBy(s),this.hiding=!0,n.stop(!0),this._hide(n,this.options.hide,function(){i._removeTooltip(e(this)),this.hiding=!1,this.closing=!1}),s.removeData("ui-tooltip-open"),this._off(s,"mouseleave focusout keyup"),s[0]!==this.element[0]&&this._off(s,"remove"),this._off(this.document,"mousemove"),t&&"mouseleave"===t.type&&e.each(this.parents,function(t,s){e(s.element).attr("title",s.title),delete i.parents[t]}),this.closing=!0,this._trigger("close",t,{tooltip:n}),this.hiding||(this.closing=!1))},_tooltip:function(t){var i=e("
    ").attr("role","tooltip").addClass("ui-tooltip ui-widget ui-corner-all ui-widget-content "+(this.options.tooltipClass||"")),s=i.uniqueId().attr("id");return e("
    ").addClass("ui-tooltip-content").appendTo(i),i.appendTo(this.document[0].body),this.tooltips[s]=t,i},_find:function(t){var i=t.data("ui-tooltip-id");return i?e("#"+i):e()},_removeTooltip:function(e){e.remove(),delete this.tooltips[e.attr("id")]},_destroy:function(){var t=this;e.each(this.tooltips,function(i,s){var n=e.Event("blur");n.target=n.currentTarget=s[0],t.close(n,!0),e("#"+i).remove(),s.data("ui-tooltip-title")&&(s.attr("title")||s.attr("title",s.data("ui-tooltip-title")),s.removeData("ui-tooltip-title"))}),this.liveRegion.remove()}})}); \ No newline at end of file diff --git a/css/main.css b/css/main.css index 5182a91..859636c 100644 --- a/css/main.css +++ b/css/main.css @@ -8,4 +8,4 @@ user-select: text; cursor:auto - } \ No newline at end of file +} diff --git a/doc/IncreaseSessionDuration.txt b/doc/IncreaseSessionDuration.txt new file mode 100644 index 0000000..881d8ef --- /dev/null +++ b/doc/IncreaseSessionDuration.txt @@ -0,0 +1,13 @@ +Increasing session duration +--------------------------- + +By default, the session duration in PHP is set to 1,440 seconds (24 +minutes). However, this is not convenient in most software +systems. Therefore, we may need to increase the duration to allow +users to have more session time. To increase the session duration, we +need to edit the variable *session.gc_maxlifetime* in **php.ini**. We +can increase its default value to whatever we want (e.g., 7200). On +Ubuntu, the file is located at */etc/php/7.2/apache2/php.ini*. On +XAMPP, the file is located at */xampp/php/php.ini*. + +*Last modified on 20 April 2022 by Umar* diff --git a/doc/Install-LRR-on-Ubuntu-VirtualBox.txt b/doc/Install-LRR-on-Ubuntu-VirtualBox.txt new file mode 100755 index 0000000..6678a61 --- /dev/null +++ b/doc/Install-LRR-on-Ubuntu-VirtualBox.txt @@ -0,0 +1,94 @@ +Contributed by Aya Boussouf 2025-05-19 + + +# ---------------------- System Update & LAMP Stack Installation ---------------------- +sudo apt update && sudo apt upgrade -y +sudo apt install apache2 mysql-server php libapache2-mod-php php-mysql unzip git -y + +# ---------------------- Start and Enable Apache & MySQL Services ---------------------- +sudo systemctl start apache2 +sudo systemctl enable apache2 + +sudo systemctl start mysql +sudo systemctl enable mysql + +# ---------------------- Configure MySQL Root User ---------------------- +sudo mysql +ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root'; +FLUSH PRIVILEGES; +exit; + +# ---------------------- Create LRR Database ---------------------- +mysql -u root -p +CREATE DATABASE lrr; +exit; + +# ---------------------- Clone LRR Project Repository ---------------------- +cd /var/www/html/ +sudo git clone http://118.25.96.118:3000/mrlan/LRR.git +sudo chown -R $USER:$USER /var/www/html/LRR + +# ---------------------- Switch to Hui-Organize Branch ---------------------- +cd /var/www/html/LRR +git branch +git checkout -b Hui-Organize +git pull origin Hui-Organize + +# ---------------------- Import SQL Data into MySQL ---------------------- +sudo mysql -u root -p lrr < /var/www/html/LRR/lrr_database.sql + +# ---------------------- Create Submission Folder for Assignments ---------------------- +cd /var/www/ +sudo mkdir lrr_submission +sudo chown -R www-data:www-data lrr_submission +sudo chmod -R g+rw lrr_submission + +# ---------------------- Configure Database Credentials in KeepItSafe.txt ---------------------- +sudo nano /var/www/lrr_submission/KeepItSafe.txt +# Add: root,root (username,password for MySQL connection) + + +# ---------------------- Configure Apache Virtual Host for LRR ---------------------- +sudo nano /etc/apache2/sites-available/LRR.conf + + ServerName localhost + DocumentRoot /var/www/html + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + +# ---------------------- Enable Apache Config and Rewrite Module ---------------------- +sudo a2ensite LRR +sudo a2enmod rewrite +sudo systemctl reload apache2 + +# ---------------------- Install Google Chrome (for Selenium Tests) ---------------------- +cd ~/Downloads +wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb +sudo apt install ./google-chrome-stable_current_amd64.deb + +# ---------------------- Install ChromeDriver ---------------------- +wget https://storage.googleapis.com/chrome-for-testing-public/136.0.7103.92/linux64/chromedriver-linux64.zip +unzip chromedriver-linux64.zip +sudo mv chromedriver-linux64/chromedriver /usr/local/bin/ +sudo chmod +x /usr/local/bin/chromedriver + +# ---------------------- Install Python, pip and Virtual Environment ---------------------- +sudo apt install python3 python3-pip -y +sudo apt install python3-venv -y + +# ---------------------- Create and Activate Virtual Environment for LRR ---------------------- +cd /var/www/html/LRR +python3 -m venv venv +source venv/bin/activate + +# ---------------------- Install Required Python Packages ---------------------- +pip install selenium +pip install pytest + +# ---------------------- Run Selenium Tests ---------------------- +cd test +# Make necessary edits in `conftest.py` and `test_lrr.py`. +pytest -v SeleniumHui/test_lrr.py + + diff --git a/doc/Issues.txt b/doc/Issues.txt index e79cc3e..b41ded8 100644 --- a/doc/Issues.txt +++ b/doc/Issues.txt @@ -1,7 +1,26 @@ +Todo +---- + +3. Filter file format and size upon upload. + +4.1. Upon Change password it must ask the old password first before new password. + +4.2. It should not use the GET REQUEST on password which puts user's data at risk since it displays in the URL. * Allow submission without file upload -* Lecturer/TA should see his/her feedback on submissions + +* Lecturer/TA should see his/her feedback on submissions + +* Remarking request details required + + +Done +---- + +1. The connect.php should not echo 'Connected' since there is a redirect already in the header.php [Resolved] + +2. The header.php is connecting to the database twice through inline connection and an external connect.php [Resolved] + +4. Added css into the header.php * Check Spelling Issues - -* Remarking request details required \ No newline at end of file diff --git a/doc/QuickStart.txt b/doc/QuickStart.txt new file mode 100644 index 0000000..195a563 --- /dev/null +++ b/doc/QuickStart.txt @@ -0,0 +1,26 @@ +Quick Start Guide + + +* Use Admin User to Create Lecturer Accounts + + Account: Admin + Password: admin@123 + +* Create a Lecturer account + +* Login to Lecturer account + +* Create a course ( You can determine whether or not students need approval before they can join the course. ) + +* As Student + + You can Sign up with your Student ID, then provide your email address and password. + +* Browse Courses by Deartment + + - Or Search by Course Course + - Join Course + - You can see the list of your courses in Course Home page + + + diff --git a/doc/Quick_Start.txt b/doc/Quick_Start.txt deleted file mode 100644 index e6df0fe..0000000 --- a/doc/Quick_Start.txt +++ /dev/null @@ -1,32 +0,0 @@ - -

    Quick Start Guide

    -
    - - -* Use Admin User to Create Lecturer Accounts - - user : Admin - Password : admin@123 - -* Create Lecturer - -* Login to Lecturer Account - - -* Create Course ( You can define whether or not students require approval to join the course ) - - -* As Student - - You can Sign up with your Student ID - Then Provide your Email and Password - -* Browse Courses by Deartment - - - Or Search by Course Course - - Join Course - - You can see the list of your courses in Course Home page - -* - - diff --git a/doc/Reset_Database.txt b/doc/ResetDatabase.txt similarity index 92% rename from doc/Reset_Database.txt rename to doc/ResetDatabase.txt index 620daa0..0b842a5 100644 --- a/doc/Reset_Database.txt +++ b/doc/ResetDatabase.txt @@ -7,8 +7,8 @@ delete from course_Group_Members_table; delete from course_students_table; delete from course_ta; -delete from extended_deadlines_table; +delete from extended_deadlines_table; delete from lab_reports_table; @@ -16,4 +16,4 @@ delete from lab_report_submissions; delete from students_data; -Delete from users_table; +delete from users_table; diff --git a/doc/ResetPassword.txt b/doc/ResetPassword.txt new file mode 100644 index 0000000..a197b1f --- /dev/null +++ b/doc/ResetPassword.txt @@ -0,0 +1,24 @@ +Resetting password +------------------ + +We can reset a user's password by directly modifying the MySQL +database table called `users_table`. More specifically, we delete +that user's information from `users_table` so that the user could sign +up again. Suppose the user's student number is 201131129138. + +To do so, LRR administrator logs in to MySQL using the following +command: `mysql -u mnc -p`. Type the correct password to access +the MySQL database. + +After that, issue the following commands in the mysql prompt. + +- `use lrr;` + +- `delete from users_table where Student_ID="201131129138";` + +The first one uses a database called lrr in MySQL. The second one +deletes a record from `users_table` where the student number is +201131129138. + + +*Last modified on 20 April 2022 by Umar* diff --git a/doc/UserDoc.md b/doc/UserDoc.md deleted file mode 100644 index 8a78d83..0000000 --- a/doc/UserDoc.md +++ /dev/null @@ -1,26 +0,0 @@ -LRR User Documentation -====================== - - -Resetting password -------------------- - -We can reset a user's password by directly modifying the MySQL database table called `users_table`. More specifically, we delete that user's information from `users_table` so that the user could sign up again. Suppose the user's student number is 201131129138. - -To do so, LRR administrator logs in to MySQL using the following command: `mysql -u username -p`. Type the correct password to access the MySQL database. - -After that, issue the following commands in the mysql prompt. - -- `use lrr;` - -- `delete from users_table where Student_ID="201131129138";` - -The first one uses a database called lrr in MySQL. The second one deletes a record from `users_table` where the student number is 201131129138. - -Increasing session duration -------------------- - -By default, the session duration in PHP is set to 1,440 seconds (24 minutes). However, this is not convenient in most software systems. Therefore, we may need to increase the duration to allow users to have more session time. To increase the session duration, we need to edit the variable *session.gc_maxlifetime* in **php.ini**. We can increase its default value to whatever we want (e.g., 7200). -On Ubuntu, the file is located at */etc/php/7.2/apache2/php.ini*. On XAMPP, the file is located at */xampp/php/php.ini*. - -*Last modified on 20 April 2022 by Umar* diff --git a/doc/ashlys_issues.txt b/doc/ashlys_issues.txt deleted file mode 100644 index 206f7b9..0000000 --- a/doc/ashlys_issues.txt +++ /dev/null @@ -1,10 +0,0 @@ -1. The connect.php should not echo 'Connected' since there is a redirect already in the header.php [Resolved] - -2. The header.php is connecting to the database twice through inline connection and an external connect.php [Resolved] - -3. Filter file format and size upon upload. - -4.1. Upon Change password it must ask the old password first before new password. -4.2. It should not use the GET REQUEST on password which puts user's data at risk since it displays in the URL. - -4. Added css into the header.php \ No newline at end of file diff --git a/index.php b/index.php index 94537b3..576d099 100644 --- a/index.php +++ b/index.php @@ -1,128 +1,79 @@ You\'ve already logged in.
    '; + exit(); } ?>


    -
    -
    -

    - -

    -
    -

    Lab Report Repository

    -

    -
    -
    -
    -
    -

    Sign in

    -
    +
    -
    +
    +
    + LRR Logo +

    +
    +

    Lab Report Repository

    +
    +
    -
    - - Student ID / Instructor Email - -
    - Password - -
    -
    -
    -
    Reset my password -
    +
    + + Sign in + + + +
    + + +
    + - '; - $_SESSION['info_login']=null; - } - - - // wrong pass - if(isset($_SESSION['wrong_pass'])) { - echo '
    '; - $_SESSION['wrong_pass']=null; - } - - - if(isset($_SESSION['infoChangePassword'])) { - echo '
    '; - $_SESSION['infoChangePassword']=null; - } - ?> - -
    -
    +
    + Sign up + +
    + Recover + + + '; + $_SESSION['info_login'] = null; + } + + + // wrong password + if(isset($_SESSION['wrong_pass'])) { + echo '
    '; + $_SESSION['wrong_pass'] = null; + } + + + if(isset($_SESSION['infoChangePassword'])) { + echo '
    '; + $_SESSION['infoChangePassword'] = null; + } + ?> + +
    -
    -
    - -
    - + LRR was originally developed in 2018 as a software engineering course project by Mohamed Nor and Elmahdi Houzi. Please submit your bug reports to Mr Lan. More information ... +
    - - - diff --git a/logout.php b/logout.php index 7ff6624..f8ffb17 100644 --- a/logout.php +++ b/logout.php @@ -1,23 +1,24 @@ - + diff --git a/lrr_database.sql b/lrr_database.sql index 22759cb..df41dae 100644 --- a/lrr_database.sql +++ b/lrr_database.sql @@ -61,7 +61,7 @@ CREATE TABLE `courses_table` ( INSERT INTO `courses_table` (`Course_ID`, `Course_Name`, `Academic_Year`, `Faculty`, `Lecturer_User_ID`, `TA_User_ID`, `Course_Code`, `URL`, `Verify_New_Members`) VALUES (10, 'Software Engineering', '2018', 'Computing', 8, 0, 'CSC1234', 'CSC12342018', '1'), -(11, 'Project Management', '2019', 'Computing', 8, 0, 'P.M2019', 'P.M20192019', '0'), +(11, 'Project Management', '2024', 'Computing', 8, 0, 'CSC1111', 'CSC11112024', '0'), (12, 'Ashly Course Testing', '2020', 'Testing', 8, 0, 'Teecloudy', 'Teecloudy2020', '1'); -- -------------------------------------------------------- @@ -136,7 +136,7 @@ INSERT INTO `course_students_table` (`Course_ID`, `Student_ID`, `ID`, `Status`) (10, '201825800050', 13, 'Joined'), (10, '201825800054', 14, 'Joined'), (12, '201632120150', 15, 'Joined'), -(12, '2016321201502', 16, 'Joined'), +(12, '201632120150', 16, 'Joined'), (12, '201825800050', 17, 'Joined'); -- -------------------------------------------------------- @@ -200,7 +200,7 @@ CREATE TABLE `lab_reports_table` ( INSERT INTO `lab_reports_table` (`Lab_Report_ID`, `Course_ID`, `Posted_Date`, `Deadline`, `Instructions`, `Title`, `Attachment_link_1`, `Attachment_link_2`, `Attachment_link_3`, `Attachment_link_4`, `Marks`, `Type`) VALUES (1, 10, '2019-01-11 16:52', '2019-02-11 17:00', 'Description of the lab....', 'Reading 1', '700IMPORTANT WORDS.txt', '', '', '', '4', 'Individual'), -(2, 10, '2019-01-17 11:12', '2019-01-25 23:59', 'Read this paper http://sunnyday.mit.edu/16.355/budgen-david.pdf', 'Reading 2', '586LRR-Test-caseS.pdf', '', '', '', '6', 'Individual'), +(2, 10, '2024-09-29 11:12', '2025-07-30 23:59', 'Read this paper http://sunnyday.mit.edu/16.355/budgen-david.pdf', 'Reading 2', '586LRR-Test-caseS.pdf', '', '', '', '6', 'Individual'), (3, 12, '2020-04-05 02:48', '2020-04-12 ', 'Do this assignment in time for testing', 'First Assignment Testing', '', '', '', '', '3', 'Group'), (4, 12, '2020-04-05 05:36', '2020-04-06 ', 'We are testing to see if the instructor can be able to modify the work', 'Second Assignment Testing', '', '', '', '', '3', 'Individual'), (5, 12, '2020-04-05 05:51', '2020-04-08 ', 'ASQDASDASCDD', 'Third Assignment Testingas', '', '', '', '', '3', 'Individual'), @@ -235,7 +235,7 @@ CREATE TABLE `lab_report_submissions` ( -- INSERT INTO `lab_report_submissions` (`Submission_ID`, `Submission_Date`, `Lab_Report_ID`, `Student_id`, `Course_Group_id`, `Attachment1`, `Notes`, `Attachment2`, `Attachment3`, `Attachment4`, `Marks`, `Status`, `Title`, `Visibility`, `Remarking_Reason`) VALUES -(1, '2019-01-17 00:00:00', 1, '201825800050', 0, 'Reading list.txt', '-', '', '', '', 5, 'Marked', 'Reading 1 submission', 'Public', ''), +(1, '2019-01-17 00:00:00', 1, '201825800050', 0, 'Reading list.txt', '-', '', '', '', NULL, 'Pending', 'Reading 1 submission', 'Public', ''), (5, '2019-01-21 08:31:00', 2, '201825800050', 0, 'Trial Balance.txt', ' - @2019-01-21 09:35 : Sorry I missed some details from your report', 'Boorka.jpg', '', '', 6, 'Marked', 'Submission x', 'Private', ''), (30, '2020-04-06 23:18:00', 3, '0', 31, '/2016321201502/First Assignment Testing/UR Diagram.pdf', '
    @2020-04-06 23:19 : ', '', '', '', 3, 'Marked', 'First Assignment Testing', 'Private', ''); @@ -259,7 +259,11 @@ INSERT INTO `students_data` (`ID`, `Student_ID`, `Passport_Number`) VALUES (1, '201825800054', 'LJ7951632'), (2, '201825800050', 'P00581929'), (3, '201632120150', 'FN524516'), -(4, '11', '11'); +(4, '202400000001', 'NA'), +(5,'201932130101',''), +(6,'201920781742',''); + + -- -------------------------------------------------------- @@ -268,7 +272,7 @@ INSERT INTO `students_data` (`ID`, `Student_ID`, `Passport_Number`) VALUES -- CREATE TABLE `users_table` ( - `User_ID` int(11) NOT NULL, + `User_ID` int(11) NOT NULL AUTO_INCREMENT, `Email` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL, `Password` varchar(250) CHARACTER SET utf8 DEFAULT NULL, `HashPassword` varchar(250) COLLATE utf8mb4_bin NOT NULL, @@ -276,7 +280,8 @@ CREATE TABLE `users_table` ( `UserType` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL, `Student_ID` varchar(500) COLLATE utf8mb4_bin DEFAULT NULL, `Passport_Number` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL, - `Status` varchar(30) COLLATE utf8mb4_bin NOT NULL DEFAULT 'Active' + `Status` varchar(30) COLLATE utf8mb4_bin NOT NULL DEFAULT 'Active', + PRIMARY KEY (`User_ID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; -- @@ -284,16 +289,37 @@ CREATE TABLE `users_table` ( -- INSERT INTO `users_table` (`User_ID`, `Email`, `Password`, `HashPassword`, `Full_Name`, `UserType`, `Student_ID`, `Passport_Number`, `Status`) VALUES -(3, 'admin@qq.com', '123', '', 'Kamal', 'Admin', '0', NULL, 'Active'), -(8, 'lanhui@qq.com', '1234', '', 'Lanhui', 'Lecturer', NULL, '123', 'Active'), -(9, 'mohamed@qq.com', '123', '', 'Mohamed', 'Student', '201825800050', 'P00581929', 'Active'), +(3, 'admin@qq.com', '$2y$10$8GCG6lTo1LFRD3bOkAyKYeOMOrFSBUgrTxaPLS5ynWN1bYDHf89pO', '', 'Kamal', 'Admin', '0', NULL, 'Active'), +(7, 'peter@qq.com', '$2y$10$8GCG6lTo1LFRD3bOkAyKYeOMOrFSBUgrTxaPLS5ynWN1bYDHf89pO', '', 'Peter', 'Lecturer', NULL, '123', 'Active'), +(8, 'lanhui@qq.com', '$2y$10$8GCG6lTo1LFRD3bOkAyKYeOMOrFSBUgrTxaPLS5ynWN1bYDHf89pO', '', 'Lanhui', 'Lecturer', NULL, '123', 'Active'), +(9, 'mohamed@qq.com', '$2y$10$8GCG6lTo1LFRD3bOkAyKYeOMOrFSBUgrTxaPLS5ynWN1bYDHf89pO', '', 'Mohamed', 'Student', '201825800050', 'P00581929', 'Active'), (10, 'mark@qq.com', '123', '', 'Mark ', 'TA', NULL, '123', 'Active'), (11, 'john@qq.com', '123', '', 'John', 'TA', NULL, '123', 'Active'), (12, 'mehdi@qq.com', '123', '', 'El-mehdi Houzi', 'Student', '201825800054', 'LJ7951632', 'Active'), (17, 'teecloudy@qq.com', '$2y$10$8WqSK7QI.3YCb2yoclqutOxyGxojncUvzhqLcE8zjlSvjBdcIQ18O', '', 'Ashly Tafadzwa Dhani', 'Student', '201632120150', NULL, 'Active'), (18, 'ashly@qq.com', 'Testing2', '', 'Ashly 2 Testing', 'Student', '2016321201502', NULL, 'Active'), -(19, '11@11.11', 'dfdf', '760a8f4f392f1f6bc3ecb118365c6cd039b59fdce96122897d5157970d9c9c129bd73b3c402dbeedd8fe94d319df7bd7de0025c22839fec06631a025ec1e0e69', '11', 'Student', '11', '', 'Active'); +(19, '11@11.11', 'dfdf', '760a8f4f392f1f6bc3ecb118365c6cd039b59fdce96122897d5157970d9c9c129bd73b3c402dbeedd8fe94d319df7bd7de0025c22839fec06631a025ec1e0e69', '11', 'Student', '11', '', 'Active'), +(20,'goodstd@qq.com','$2y$10$RXIjONOK.mw74pSxPrsnGuccQsFq4O4.e71vaxfGHHhtzHREtfqkG','','Good Std','Student','201932130101',NULL,'Active'),(21,'goodstd2@qq.com','$2y$10$kmqrGuZd7hCiiaHFDQr2vObpD7BgEnCKlgQ/EcLHYnsQenMbNKcHy','','Good Std2','Student','201920781742',NULL,'Active'); +CREATE TABLE `password_recovery_security_questions` ( + `user_id` int NOT NULL, + `user_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, + `student_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, + `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, + `question1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, + `answer1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, + `question2` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, + `answer2` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, + PRIMARY KEY (`user_id`), + CONSTRAINT `password_recovery_security_questions_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users_table` (`User_ID`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; + +INSERT INTO `password_recovery_security_questions` VALUES +(3,'Admin',NULL,'admin@qq.com','What is the name of your first pet?','mimi','What was the name of your first school?','zjnu'), +(7,'Lecturer',NULL,'peter@qq.com','What was the name of your first best friend?','chen','What is your favorite food?','hotpot'), +(8,'Lecturer',NULL,'lanhui@qq.com','What is the name of the town where you were born?','jinhua','What is your favorite movie?','titanic'), +(9,'Student','201825800050','mohamed@qq.com','What was the make and model of your first car?','audi','What is the name of your favorite teacher?','chen'), +(21,'Student','201920781742','goodstd2@qq.com','What is the name of the town where you were born?','wenzhou','What is your favorite food?','hotpot'); -- -- Indexes for dumped tables -- @@ -347,12 +373,6 @@ ALTER TABLE `lab_report_submissions` ALTER TABLE `students_data` ADD PRIMARY KEY (`ID`); --- --- Indexes for table `users_table` --- -ALTER TABLE `users_table` - ADD PRIMARY KEY (`User_ID`); - -- -- AUTO_INCREMENT for dumped tables -- @@ -410,12 +430,7 @@ ALTER TABLE `students_data` -- ALTER TABLE courses_table MODIFY Course_Name varchar(500); --- --- AUTO_INCREMENT for table `users_table` --- -ALTER TABLE `users_table` - MODIFY `User_ID` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=20; -COMMIT; + /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; diff --git a/mail_spam.html b/mail_spam.html deleted file mode 100644 index 7589930..0000000 --- a/mail_spam.html +++ /dev/null @@ -1,14 +0,0 @@ - \ No newline at end of file diff --git a/phpinfo.php b/phpinfo.php deleted file mode 100644 index 74a8ccc..0000000 --- a/phpinfo.php +++ /dev/null @@ -1,7 +0,0 @@ -'; -echo 'LOADED EXTENSIONS:
    '; -print_r(get_loaded_extensions()); -echo ''; -echo phpinfo(); -?> \ No newline at end of file diff --git a/recover_password.php b/recover_password.php deleted file mode 100644 index d9ae8a2..0000000 --- a/recover_password.php +++ /dev/null @@ -1,60 +0,0 @@ - - - - - -
    - -
    - -
    - -

    Reset my password

    -
    - -
    -
    - - Student number -
    - Email -
    - - -
    - -'; - $_SESSION['info_recover_password']=null; -} - -?> - - diff --git a/signup.php b/signup.php index ae17b5e..5763947 100644 --- a/signup.php +++ b/signup.php @@ -1,64 +1,53 @@ - - - - -
    - -
    - -
    - -

    Please fill in each field below

    -
    - -
    - -
    - - Full Name - - - Student ID - - - Email - - - Password (must include uppercase and lowercase letters, digits and special characters) - - - Confirm Password - -
    - - '; - $_SESSION['info_signup'] = null; - } - ?> -
    - -
    -
    -
    -
    - \ No newline at end of file + + + + +


    + +
    + +
    + +
    + +
    + +
    + Sign up + + + + Full Name +
    + + Student ID +
    + + Email +
    + + Password +
    + + Confirm Password +
    +
    + + + '; + $_SESSION['info_signup'] = null; + } + ?> + +
    +
    +
    +
    + diff --git a/test/SeleniumAya/helper.py b/test/SeleniumAya/helper.py new file mode 100644 index 0000000..6220b3f --- /dev/null +++ b/test/SeleniumAya/helper.py @@ -0,0 +1,37 @@ +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import NoSuchElementException, UnexpectedAlertPresentException + + +def login(driver, url, username, password): + try: + driver.get(url) + + # Fill in the login form + user_input = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "user_name")) + ) + user_input.send_keys(username) + + password_input = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "user_password")) + ) + password_input.send_keys(password) + + # Click the login button + login_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "login_btn")) + ) + login_button.click() + except (NoSuchElementException, UnexpectedAlertPresentException) as e: + return f"Error: {str(e)}" + + +def logout(driver): + logout_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable( + (By.XPATH, "//a[contains(@class, 'nav-link') and contains(@href, 'logout.php')]") + ) + ) + logout_button.click() diff --git a/test/SeleniumAya/test_bug48.py b/test/SeleniumAya/test_bug48.py new file mode 100644 index 0000000..d43d7b6 --- /dev/null +++ b/test/SeleniumAya/test_bug48.py @@ -0,0 +1,156 @@ +from helper import login, logout +import time +import pytest +from selenium.webdriver.common.by import By +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import Select + +def test_attempt_recovery_with_unanswered_security_questions(driver, url, restore_database): + # Student goodstd@qq.com with no answered questions + driver.get(url) + driver.maximize_window() + + elem = driver.find_element(By.ID, 'goRecover') + elem.click() + elem = driver.find_element(By.NAME, 'email') + elem.send_keys("goodstd@qq.com") + elem = driver.find_element(By.ID, 'rec') + elem.click() + + wait = WebDriverWait(driver, 10) + alert_elem = wait.until(EC.presence_of_element_located((By.CLASS_NAME, "alert-warning"))) + assert "no security questions found for this user." in alert_elem.text.lower() + + driver.quit() + +def test_set_security_questions(driver, url, restore_database): + # Student goodstd@qq.com logs in + driver.get(url) + driver.maximize_window() + login(driver, url, 'goodstd@qq.com', '[123Abc]') + question1 = driver.find_element(By.ID, 'security_question1') + question2 = driver.find_element(By.ID, 'security_question2') + select_question1 = Select(question1) + select_question1.select_by_index(1) + select_question2 = Select(question2) + select_question2.select_by_index(2) + answer1 = driver.find_element(By.ID, 'security_answer1') + answer1.send_keys("mImI") + answer2 = driver.find_element(By.ID, 'security_answer2') + answer2.send_keys("TitaNic") + submit_button = driver.find_element(By.ID, 'submit_recovery') + submit_button.click() + + # Assertion: Check if password recovery confirmation or success message appears + WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, 'alertgood'))) + recovery_success = driver.find_element(By.ID, 'alertgood') + assert recovery_success.is_displayed(), "Password recovery failed or confirmation message not displayed." + + logout(driver) + + #Log in Student account + login(driver, url, 'goodstd@qq.com', '[123Abc]') + elems = driver.find_elements(By.CLASS_NAME, 'nav-link') + assert 'Student ID' in elems[0].text + assert 'Good Std' in elems[0].text + + driver.quit() + + +def test_password_recover_wrong_answers(driver, url, restore_database): + # Student goodstd2@qq.com recover password --> wrong answers + driver.get(url) + driver.maximize_window() + elem = driver.find_element(By.ID, 'goRecover') + elem.click() + elem = driver.find_element(By.NAME, 'email') + elem.send_keys("goodstd2@qq.com") + elem = driver.find_element(By.ID, 'rec') + elem.click() + elem = driver.find_element(By.NAME, 'answer1') + elem.send_keys("wrong") + elem = driver.find_element(By.NAME, 'answer2') + elem.send_keys("wrong") + elem = driver.find_element(By.ID, 'sub') + elem.click() + + driver.quit() + +def test_recover_password_with_weak_password(driver, url, restore_database): + driver.get(url) + driver.maximize_window() + elem = driver.find_element(By.ID, 'goRecover') + elem.click() + elem = driver.find_element(By.NAME, 'email') + elem.send_keys("goodstd2@qq.com") + elem = driver.find_element(By.ID, 'rec') + elem.click() + elem = driver.find_element(By.NAME, 'answer1') + elem.send_keys("Wenzhou") + elem = driver.find_element(By.NAME, 'answer2') + elem.send_keys("HotPot") + elem = driver.find_element(By.ID, 'sub') + elem.click() + elem = driver.find_element(By.NAME, 'new_password') + elem.send_keys("123") + elem = driver.find_element(By.NAME, 'confirm_password') + elem.send_keys("123") + elem = driver.find_element(By.ID, "butt") + elem.click() + + wait = WebDriverWait(driver, 10) + alert_elem = wait.until(EC.presence_of_element_located((By.CLASS_NAME, "alert-danger"))) + assert "password must be at least 8 characters long and include uppercase and lowercase letters, numbers, and special characters." in alert_elem.text.lower() + + driver.quit() + +def test_recover_password_successfully(driver, url, restore_database): + # Student goodstd2@qq.com recover password ---> correct answers + driver.get(url) + driver.maximize_window() + elem = driver.find_element(By.ID, 'goRecover') + elem.click() + elem = driver.find_element(By.NAME, 'email') + elem.send_keys("goodstd2@qq.com") + elem = driver.find_element(By.ID, 'rec') + elem.click() + elem = driver.find_element(By.NAME, 'answer1') + elem.send_keys("wenzhou") + elem = driver.find_element(By.NAME, 'answer2') + elem.send_keys("hotpot") + elem = driver.find_element(By.ID, 'sub') + elem.click() + elem = driver.find_element(By.NAME, 'new_password') + elem.send_keys("123Abc!!") + elem = driver.find_element(By.NAME, 'confirm_password') + elem.send_keys("123Abc!!") + elem = driver.find_element(By.ID, "butt") + elem.click() + + #Log in Student account + login(driver, url, 'goodstd2@qq.com', '123Abc!!') + elems = driver.find_elements(By.CLASS_NAME, 'nav-link') + assert 'Student ID' in elems[0].text + assert 'Good Std2' in elems[0].text + driver.quit() + +def test_recovery_invalid_user_cannot_recover(driver, url, restore_database): + # Unrecognizable user cannot recover + driver.get(url) + driver.maximize_window() + elem = driver.find_element(By.ID, 'goRecover') + elem.click() + elem = driver.find_element(By.NAME, 'email') + elem.send_keys("goodstd3@qq.com") + elem = driver.find_element(By.ID, 'rec') + elem.click() + + wait = WebDriverWait(driver, 10) + alert_elem = wait.until(EC.presence_of_element_located((By.CLASS_NAME, "alert-danger"))) + assert "email address is not recognized." in alert_elem.text.lower() + + driver.quit() + + + diff --git a/test/SeleniumEden/readme.md b/test/SeleniumEden/readme.md new file mode 100644 index 0000000..ea8f26f --- /dev/null +++ b/test/SeleniumEden/readme.md @@ -0,0 +1,46 @@ +Sign-Up Automation Test Script +Overview +This script automates the sign-up process for a web application using Selenium WebDriver. It tests whether form values are retained correctly if the initial sign-up attempt fails and requires modification of the student ID before resubmission. + +Prerequisites +Python 3 +Selenium library +Chrome WebDriver +A running instance of the web application on http://localhost:8080 + +Configuration +Web Application URL: Make sure the web application is running at http://localhost:8080. Adjust the URL in the script if necessary. + +Update Element Locators: Ensure the IDs used in the script (signup_link, signup_form, full_name, student_id, email, password1, password2, signup_btn) match those in your web application. + +Run the script: + +Script Logic + +Open the Login Page: The script navigates to the login page of the web application. +Click the "Sign Up" Link: It waits for the "Sign Up" link to appear and clicks it to navigate to the sign-up page. +Fill Out the Sign-Up Form: It fills out the form fields (Full Name, Student ID, Email, Password). +Submit the Form: It submits the form and waits for the result. +Check for Sign-Up Failure: If sign-up fails, it checks if form values are retained. +Modify Student ID: If the form values are retained, it modifies the student ID and resubmits the form. +Verify Retained Values: It verifies if other fields retain their values after modifying the student ID. +Print Retained Values: If the second attempt is successful, it prints the retained form values. +Close the Browser: The browser is closed at the end of the script execution. + + + +The script provides the following output: + +Sign-Up Successful: Indicates the sign-up was successful on the first attempt. +Sign-Up Failed: Indicates the sign-up failed on the first attempt and checks for retained values. +Second Sign-Up Attempt Successful: Indicates the second sign-up attempt was successful and prints the retained values. +Second Sign-Up Attempt Failed: Indicates the second sign-up attempt also failed, suggesting further investigation is needed. +Notes +Form Field IDs: Ensure the IDs used in the script match those in your web application. +Password Fields: The script intentionally does not print password fields for security reasons. +Adjust Wait Times: Modify the wait times as needed depending on your application's response times. +Troubleshooting +Element Not Found: Verify the element IDs and update them in the script. +WebDriver Errors: Ensure the Chrome WebDriver is installed and matches your Chrome browser version. +Connection Errors: Ensure the web application is running and accessible at the specified URL. +Contributing diff --git a/test/SeleniumEden/test.py b/test/SeleniumEden/test.py new file mode 100644 index 0000000..7c6522d --- /dev/null +++ b/test/SeleniumEden/test.py @@ -0,0 +1,88 @@ +import time # Import time module for waiting +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC + +# New instance of the Chrome driver +driver = webdriver.Chrome() + +try: + # Step 1: Open the login page + driver.get("http://localhost:8080/lrr/lrr/admin.php") # Replace with your actual login page URL + + # Step 2: Wait for the login page to fully load and locate the "Sign Up" link + sign_up_link = WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.ID, "signup_link")) + ) + + # Step 3: Click the "Sign Up" link to navigate to the sign-up page + sign_up_link.click() + + # Step 4: Wait for the sign-up page to fully load + WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.ID, "signup_form")) + ) + + # Step 5: Fill out the sign-up form + driver.find_element(By.ID, "full_name").send_keys("John Doe") + driver.find_element(By.ID, "student_id").send_keys("12345678") + driver.find_element(By.ID, "email").send_keys("john.doe@example.com") + driver.find_element(By.ID, "password1").send_keys("Password123!") + driver.find_element(By.ID, "password2").send_keys("Password123!") + + # Step 6: Submit the sign-up form + driver.find_element(By.ID, "signup_btn").click() + + # Step 7: Wait for the sign-up result + WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.TAG_NAME, "body")) + ) + + # Check if sign-up failed + if "alert-danger" in driver.page_source: + print("Sign-up failed. Checking if form values are retained...") + + # Wait for a few seconds (adjust as needed) + time.sleep(3) + + # Modify the student ID again + driver.find_element(By.ID, "student_id").clear() + driver.find_element(By.ID, "student_id").send_keys("87654321") + + # Verify if the other fields retain their values + assert driver.find_element(By.ID, "full_name").get_attribute("value") == "John Doe" + assert driver.find_element(By.ID, "email").get_attribute("value") == "john.doe@example.com" + assert driver.find_element(By.ID, "password1").get_attribute("value") == "" + assert driver.find_element(By.ID, "password2").get_attribute("value") == "" + + # Resubmit the form + driver.find_element(By.ID, "signup_btn").click() + + # Wait for the result again + WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.TAG_NAME, "body")) + ) + + # Check for success or failure after second attempt + if "alert-danger" in driver.page_source: + print("Second sign-up attempt failed. Further investigation needed.") + + + # Print the retained values + print("Retained form values after second attempt:") + print("Full Name:", driver.find_element(By.ID, "full_name").get_attribute("value")) + print("Email:", driver.find_element(By.ID, "email").get_attribute("value")) + # Password fields might be intentionally cleared, so they won't be printed here + print("Modified Student ID:", driver.find_element(By.ID, "student_id").get_attribute("value")) + + else: + print("Second sign-up attempt successful!") + else: + print("Sign-up successful!") + + + +finally: + # Close the browser + driver.quit() \ No newline at end of file diff --git a/test/SeleniumEden/test_results.txt b/test/SeleniumEden/test_results.txt new file mode 100644 index 0000000..c0131a0 --- /dev/null +++ b/test/SeleniumEden/test_results.txt @@ -0,0 +1,6 @@ +Sign-up failed. Checking if form values are retained... +Second sign-up attempt failed. Further investigation needed. +Retained form values after second attempt: +Full Name: John Doe +Email: john.doe@example.com +Modified Student ID: 87654321 diff --git a/test/SeleniumHui/helper.py b/test/SeleniumHui/helper.py new file mode 100644 index 0000000..6220b3f --- /dev/null +++ b/test/SeleniumHui/helper.py @@ -0,0 +1,37 @@ +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import NoSuchElementException, UnexpectedAlertPresentException + + +def login(driver, url, username, password): + try: + driver.get(url) + + # Fill in the login form + user_input = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "user_name")) + ) + user_input.send_keys(username) + + password_input = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "user_password")) + ) + password_input.send_keys(password) + + # Click the login button + login_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "login_btn")) + ) + login_button.click() + except (NoSuchElementException, UnexpectedAlertPresentException) as e: + return f"Error: {str(e)}" + + +def logout(driver): + logout_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable( + (By.XPATH, "//a[contains(@class, 'nav-link') and contains(@href, 'logout.php')]") + ) + ) + logout_button.click() diff --git a/test/SeleniumHui/test_lrr.py b/test/SeleniumHui/test_lrr.py new file mode 100644 index 0000000..2c191a6 --- /dev/null +++ b/test/SeleniumHui/test_lrr.py @@ -0,0 +1,364 @@ +from helper import login, logout +import time +import pytest +from selenium.webdriver.common.by import By +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC + + +def test_admin_can_create_lecturer_account(driver, url, admin_username, admin_password, restore_database): + # Administrator (admin@qq.com, password 123) logs in + driver.maximize_window() + login(driver, url, admin_username, admin_password) + + # Create a Lecturer account for Mr Lan (mrlan@qq.com, password [123Abc!]) + tab = driver.find_element(By.ID, 'tab_ins_accounts') + tab.click() + elem = driver.find_element(By.NAME, 'fullname') + elem.send_keys('Mr Lan') + elem = driver.find_element(By.NAME, 'email') + elem.send_keys('mrlan@qq.com') + elem = driver.find_element(By.NAME, 'password') + elem.send_keys('123Abc!!') + radio_button = driver.find_element(By.NAME, 'type') + radio_button.click() + button = driver.find_element(By.NAME, 'create_btn') + button.click() + + # Log out Admin account + logout(driver) + + # Log in Lecturer account + login(driver, url, 'mrlan@qq.com', '123Abc!!') + #elems = driver.find_elements(By.CLASS_NAME, 'nav-link') + #assert '(Lecturer)' in elems[0].text + #assert 'Mr Lan' in elems[0].text + wait = WebDriverWait(driver, 10) + wait.until(EC.url_contains('SecurityQuestions.php')) + + driver.quit() + + +def test_lecturer_can_create_course(driver, url, restore_database): + # Lecturer lanhui@qq.com logs in + driver.maximize_window() + login(driver, url, 'lanhui@qq.com', '123') + + # Create a course called CSC1001 Advanced Software Engineering, 2024 + elem = driver.find_element(By.NAME, 'name') + elem.send_keys('Advanced Software Engineering') + elem = driver.find_element(By.NAME, 'code') + elem.send_keys('CSC1001') + elem = driver.find_element(By.NAME, 'academic') + elem.send_keys('2004') + elem = driver.find_element(By.NAME, 'faculty') + elem.send_keys('School of Computer Science and Technology') + elem = driver.find_element(By.CLASS_NAME, 'btn-primary') + elem.click() + elems = driver.find_elements(By.CLASS_NAME, 'btn-default') + last_elem = elems[-1] + assert 'Advanced Software Engineering' in last_elem.text + assert '(CSC1001)' in last_elem.text + + # Logout + logout(driver) + driver.quit() + + +def test_lecturer_can_post_assignment(driver, url, restore_database): + # Lecturer lanhui@qq.com logs in + driver.maximize_window() + login(driver, url, 'lanhui@qq.com', '123') + + # Create an assignment called Take-home quiz 1 for course (CSC1111) - Project Management + elem = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.XPATH, '//div[@class="col-md-8"]/a[1]/div')) + ) + elem.click() + elem = driver.find_element(By.NAME, 'deadlinedate') + elem.send_keys('002025/05/30') + elem = driver.find_element(By.NAME, 'deadlinetime') + elem.send_keys('23:59') + elem = driver.find_element(By.NAME, 'title') + elem.send_keys('Take-home quiz 1') + elem = driver.find_element(By.NAME, 'instructions') + elem.send_keys('This is a closed-book quiz.') + elem = driver.find_element(By.NAME, 'marks') + elem.send_keys('10') + radio_button = driver.find_element(By.NAME, 'type') + radio_button.click() + elem = driver.find_element(By.CLASS_NAME, 'btn-primary') + elem.click() + + # Check if the assignment has been successfully posted + elem = driver.find_element(By.CLASS_NAME, 'card-title') + assert 'Take-home quiz 1 (10 Marks, Individual)' in elem.text + elem = driver.find_element(By.CLASS_NAME, 'text-muted') + assert 'Deadline: 2025-05-30' in elem.text + driver.quit() + + +def test_lecturer_can_add_student_numbers(driver, url, restore_database): + # Lecturer lanhui@qq.com logs in + driver.maximize_window() + login(driver, url, 'lanhui@qq.com', '123') + + # Add ASE student numbers + student_numbers = ''' + 202420781739 + 202420781740 + 202420781741 + 202420781742 + 202420781743 + 202420781745 + 202420581366 + 202420581368 + 202420581369 + 202420581370 + 202420581372 + 202420581373 + 202420581374 + 202420581376 + 202420581378 + 202420581381 + ''' + elem = driver.find_element(By.ID, 'admin_tab') + elem.click() + elem = driver.find_element(By.NAME, 'users') + elem.send_keys(student_numbers) + elem = driver.find_element(By.ID, 'register_btn') + elem.click() + + elems = driver.find_elements(By.CSS_SELECTOR, 'p') + added = 0 + student_lst = [number.strip() for number in student_numbers.strip().split('\n')] + print(student_lst) + for student_no in student_lst: + for elem in elems: + if student_no in elem.text and 'added' in elem.text: + added += 1 + break + assert added == len(student_lst) + driver.quit() + + +def test_student_with_valid_student_number_can_sign_up(driver, url, restore_database): + # Student with recognizable student number 202400000001 can sign up an account + driver.get(url) + driver.maximize_window() + elem = driver.find_element(By.ID, 'signup_link') + elem.click() + elem = driver.find_element(By.NAME, 'fullname') + elem.send_keys('Good Student') + elem = driver.find_element(By.NAME, 'user_student_id') + elem.send_keys('202400000001') + elem = driver.find_element(By.NAME, 'email') + elem.send_keys('goodstudent@qq.com') + elem = driver.find_element(By.NAME, 'password') + elem.send_keys('[123Abc]') + elem = driver.find_element(By.NAME, 'confirmpassword') + elem.send_keys('[123Abc]') + elem = driver.find_element(By.ID, 'signup_btn') + elem.click() + driver.get(url + 'logout.php') + + # Log in Student account + login(driver, url, '202400000001', '[123Abc]') + #elems = driver.find_elements(By.CLASS_NAME, 'nav-link') + #assert 'Student ID' in elems[0].text + #assert 'Good Student' in elems[0].text + wait = WebDriverWait(driver, 10) + wait.until(EC.url_contains('SecurityQuestions.php')) + driver.quit() + + +def test_student_with_invalid_student_number_cannot_sign_up(driver, url, restore_database): + # Student with unrecognizable student number cannot sign up an account + driver.get(url) + driver.maximize_window() + elem = driver.find_element(By.ID, 'signup_link') + elem.click() + elem = driver.find_element(By.NAME, 'fullname') + elem.send_keys('Good Student') + elem = driver.find_element(By.NAME, 'user_student_id') + elem.send_keys('202400000002') + elem = driver.find_element(By.NAME, 'email') + elem.send_keys('goodstudent@qq.com') + elem = driver.find_element(By.NAME, 'password') + elem.send_keys('[123Abc]') + elem = driver.find_element(By.NAME, 'confirmpassword') + elem.send_keys('[123Abc]') + elem = driver.find_element(By.ID, 'signup_btn') + elem.click() + + # Log in Student account + login(driver, url, '202400000002', '[123Abc]') + elems = driver.find_elements(By.CLASS_NAME, 'nav-link') + assert not 'Student ID' in elems[0].text + assert not 'Good Student' in elems[0].text + driver.quit() + + +def test_student_with_weak_password_cannot_sign_up(driver, url, restore_database): + driver.get(url) + driver.maximize_window() + weak_password = '123Abc' + elem = driver.find_element(By.ID, 'signup_link') + elem.click() + elem = driver.find_element(By.NAME, 'fullname') + elem.send_keys('Good Student') + elem = driver.find_element(By.NAME, 'user_student_id') + elem.send_keys('202400000001') + elem = driver.find_element(By.NAME, 'email') + elem.send_keys('goodstudent@qq.com') + elem = driver.find_element(By.NAME, 'password') + elem.send_keys(weak_password) + elem = driver.find_element(By.NAME, 'confirmpassword') + elem.send_keys(weak_password) + elem = driver.find_element(By.ID, 'signup_btn') + elem.click() + + # Log in Student account + login(driver, url, '202400000001', weak_password) + elems = driver.find_elements(By.CLASS_NAME, 'nav-link') + assert not 'Student ID' in elems[0].text + assert not 'Good Student' in elems[0].text + driver.quit() + + +def test_student_can_join_course(driver, url, restore_database): + # Student can join (CSC1111) - Project Management + login(driver, url, '201825800050', '123') + driver.maximize_window() + + # Search for CSC1111 + elem = driver.find_element(By.NAME, 'search') + elem.send_keys('CSC1111') + elem = driver.find_element(By.CLASS_NAME, 'btn-primary') + elem.click() + elems = driver.find_elements(By.CLASS_NAME, 'btn-default') + assert 'CSC1111' in elems[0].text + + # Join + elem = driver.find_element(By.CLASS_NAME, 'btn-success') # find the green Join button + elem.click() + + # Log out, then log in to check the course-joining status + logout(driver) + login(driver, url, '201825800050', '123') + elems = driver.find_elements(By.CLASS_NAME, 'btn-default') + assert 'CSC1111' in elems[0].text + assert 'Project Management' in elems[0].text + assert 'Joined' in elems[0].text + driver.quit() + + +def test_student_can_submit_assignment(driver, url, restore_database): + ''' Note: Make sure the fields Posted_Date and Deadline in the second row of lab_reports_table are in the current year''' + # Student can submit assignment for CSC1111 + login(driver, url, '201825800050', '123') + driver.maximize_window() + + # Enter into the course and the find the assignment + elems = driver.find_elements(By.CLASS_NAME, 'btn-default') + elems[1].click() + + elem = driver.find_element(By.XPATH, '//div[@id="menu1"]/div/div/p/a[text()="Submit"]') # find the submit button + elem.click() + + # Fill submission title, attach file, and submit + elem = driver.find_element(By.NAME, 'title') + elem.send_keys('Assignment submission from Mohamed') + elem = driver.find_element(By.NAME, 'attachment1') + elem.send_keys('/var/www/html/LRR/test/SeleniumHui/helper.py') # attach a file + elem = driver.find_element(By.XPATH, '//form/button') + elem.click() + + # Go the Submitted tab + elem = driver.find_element(By.ID, 'myTab') + elems = elem.find_elements(By.CLASS_NAME, 'nav-link') + elems[2].click() + + elem = driver.find_element(By.XPATH, '//div[@id="menu3"]/div') + assert 'Reading 2 (6 Marks)' in elem.text + assert 'SUBMITTED' in elem.text + assert 'helper.py' in elem.text + + driver.quit() + + +def test_student_can_request_remarking(driver, url, restore_database): + # Student logs in + login(driver, url, '201825800050', '123') + driver.maximize_window() + + # Enter into the course + elems = driver.find_elements(By.CLASS_NAME, 'btn-default') + elems[1].click() + + # Go the Marked tab + elem = driver.find_element(By.ID, 'myTab') + elems = elem.find_elements(By.CLASS_NAME, 'nav-link') + elems[3].click() + + # Send remarking request + remarking_buttons = driver.find_elements(By.CLASS_NAME, 'btn-light') + remarking_buttons[0].click() + alert = driver.switch_to.alert + alert.send_keys('I need higher marks, teacher.') + alert.accept() + + elem = driver.find_element(By.XPATH, '//div[@id="menu4"]/div/div/p/span') + assert 'Remarking request sent' == elem.text + + driver.quit() + + +def test_lecturer_can_mark_assignment(driver, url, restore_database): + # Lecturer lanhui@qq.com logs in + driver.maximize_window() + login(driver, url, 'lanhui@qq.com', '123') + + # Enter into the course and the find the assignment + elem = driver.find_element(By.XPATH, '//div[1]/a[3]/div') # course Software Engineering + elem.click() + + elem = driver.find_element(By.XPATH, '//div[2]/div[2]/div/a[2]') # View link + elem.click() + + elem = driver.find_element(By.CLASS_NAME, 'btn-primary') # Blue Mark button + elem.click() + + # Submit mark and comment + elem = driver.find_element(By.NAME, 'marks') + elem.send_keys('1') + elem = driver.find_element(By.NAME, 'feedback') + elem.send_keys('Inadequate') + form = driver.find_element(By.ID, 'submit-form') + form.submit() + + elem = driver.find_element(By.ID, 'myTab') + elems = elem.find_elements(By.CLASS_NAME, 'nav-link') + assert 'Marked submissions (1)' == elems[1].text + elems[1].click() + elem = driver.find_element(By.XPATH, "//div[@id='menu2']/div/b") + assert 'Reading 1 submission' in elem.text + + driver.quit() + + +def test_lecturer_cannot_see_tas_not_from_his_course(driver, url, restore_database): + # Lecturer lanhui@qq.com logs in + driver.maximize_window() + login(driver, url, 'peter@qq.com', '123') + + elem = driver.find_element(By.ID, 'admin_tab') + elem.click() + tab = driver.find_element(By.ID, 'existing_accounts_tab') + tab.click() + elem = driver.find_element(By.ID, 'tab-existing-accounts') + assert 'No TA' in elem.text + + # Logout + logout(driver) + driver.quit() diff --git a/test/SeleniumMpiana/helper.py b/test/SeleniumMpiana/helper.py new file mode 100644 index 0000000..bea59f9 --- /dev/null +++ b/test/SeleniumMpiana/helper.py @@ -0,0 +1,33 @@ +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import NoSuchElementException, UnexpectedAlertPresentException + + +def login(driver, url, username, password): + try: + driver.get(url) + + # Fill in the login form + user_input = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "user_name")) + ) + user_input.send_keys(username) + + password_input = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "user_password")) + ) + password_input.send_keys(password) + + # Click the login button + login_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "login_btn")) + ) + login_button.click() + + # Wait for the admin_tab to become clickable + admin_tab = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "admin_tab")) + ) + except (NoSuchElementException, UnexpectedAlertPresentException) as e: + return f"Error: {str(e)}" diff --git a/test/SeleniumMpiana/test_assign_ta.py b/test/SeleniumMpiana/test_assign_ta.py new file mode 100644 index 0000000..50d9e49 --- /dev/null +++ b/test/SeleniumMpiana/test_assign_ta.py @@ -0,0 +1,125 @@ +import pytest +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import Select +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import NoSuchElementException, UnexpectedAlertPresentException +from selenium.webdriver.common.keys import Keys + +# New instance of the Chrome driver +driver = webdriver.Chrome() + +# Open the login page +driver.get("http://localhost/lrr/admin.php") + +# Credentials for login +username = "lanhui@qq.com" +password = "admin123" + +def login(driver, username, password): + try: + # Fill in the login form + user_input = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "user_name")) + ) + user_input.send_keys(username) + + password_input = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "user_password")) + ) + password_input.send_keys(password) + + # Click the login button + login_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "login_btn")) + ) + login_button.click() + + # Wait for the admin_tab to become clickable + admin_tab = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "admin_tab")) + ) + + return True + + except (NoSuchElementException, UnexpectedAlertPresentException) as e: + return f"Error: {str(e)}" + +# Call the login function +login_result = login(driver, username, password) + +# Click on admin_tab after successful login +if login_result: + admin_tab = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "admin_tab")) + ) + admin_tab.click() + + # Optionally, wait for the Admin.php page to load + admin_url = "http://localhost/lrr/Admin.php" + WebDriverWait(driver, 15).until( + EC.url_to_be(admin_url) + ) + +print(login_result) + +def assign_ta(driver, course_id, ta_name): + try: + # Locate the form and select the TA + ta_form = WebDriverWait(driver, 15).until( + EC.presence_of_element_located((By.XPATH, f"//form[@id='drop_menu_form_{course_id}']")) + ) + + ta_dropdown = Select(ta_form.find_element(By.XPATH, ".//select[@name='ta']")) + ta_dropdown.select_by_visible_text(ta_name) + + # Submit the form using JavaScript + driver.execute_script("arguments[0].submit();", ta_form) + + # Wait for an expected alert and accept it + WebDriverWait(driver, 10).until(EC.alert_is_present()) + alert = driver.switch_to.alert + alert_text = alert.text + alert.accept() + + return alert_text + + except UnexpectedAlertPresentException as e: + # Unexpected alert, handle it as an error + return f"Error: Unexpected alert - {str(e)}" + + except (NoSuchElementException, Exception) as e: + return f"Error: {str(e)}" + + + +# The courses and test cases to test +courses_to_test = [ + {"id": 1, "name": "Teecloudy - Ashly Course Testing", "ta_assignments": {"JAMES": "Ta assigned successfully."}}, + {"id": 2, "name": "P.M2019 - Project Management", "ta_assignments": {"JAMES": "The selected TA is already assigned to this course."}}, +] + +# Execute the tests +@pytest.mark.parametrize("course", courses_to_test) +def test_assign_ta(course): + for ta_name, expected_result in course["ta_assignments"].items(): + alert_text = assign_ta(driver, course["id"], ta_name) + # ----- ---- Print the raw strings for debugging ----- ---- --- + test_case_number = courses_to_test.index(course) + 1 + print(f"Test Case {test_case_number} - {course['name']} -- {ta_name}: Expected Result={expected_result}, Actual Alert Text={alert_text}") + + # Determine the result based on the comparison + if expected_result.lower() in alert_text.lower(): + result = "Passed" + else: + result = "Failed" + + # Write the result to a test file with test case number --- + with open("test_results.txt", "a") as file: + file.write(f"Test Case {test_case_number} - {course['name']} -- {ta_name}: Result={result}, Expected Result={expected_result}, Actual Alert Text={alert_text}\n") + + # Print the result to the console --- + print(f"Test Case {test_case_number} - {course['name']} -- {ta_name}: Result={result}, Expected Result={expected_result}, Actual Alert Text={alert_text}") + + assert result == "Passed", f"Test Case {test_case_number} failed: Result={result}, Expected Result={expected_result}, Actual Alert Text={alert_text}" diff --git a/test/SeleniumMpiana/test_bug418_yaaqob.py b/test/SeleniumMpiana/test_bug418_yaaqob.py new file mode 100644 index 0000000..bb1a95d --- /dev/null +++ b/test/SeleniumMpiana/test_bug418_yaaqob.py @@ -0,0 +1,122 @@ +import pytest +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import Select +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import NoSuchElementException, UnexpectedAlertPresentException +from helper import login + +@pytest.mark.parametrize("course_id, course_name, ta_name", [(1, "Teecloudy - Ashly Course Testing", "Mark")]) +def test_assign_a_new_ta_to_a_course(course_id, course_name, ta_name, driver, url, admin_username, admin_password, restore_database): + try: + driver.maximize_window() + + login(driver, url, admin_username, admin_password) + + admin_tab = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "admin_tab")) + ) + admin_tab.click() + + # Locate the form and select the TA + ta_form = WebDriverWait(driver, 15).until( + EC.presence_of_element_located((By.XPATH, f"//form[@id='drop_menu_form_{course_id}']")) + ) + + ta_dropdown = Select(ta_form.find_element(By.XPATH, ".//select[@name='ta']")) + ta_dropdown.select_by_visible_text(ta_name) + + # Submit the form using JavaScript + driver.execute_script("arguments[0].submit();", ta_form) + + # find table courses + table_courses = WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.XPATH, ".//*[@id='tab-existing-courses']/table")) + ) + # find the row with matching course_name + course_row = table_courses.find_element(By.XPATH, f".//tr[td='{course_name}']") + # find the column with TA name + ta_column = course_row.find_element(By.XPATH, ".//td[4]") + + # assert the TA name in the column + assert ta_name in ta_column.text, f"Error: TA name {ta_name} not found in the column {ta_column.text}" + + except NoSuchElementException as e: + return f"Error: {str(e)}" + except UnexpectedAlertPresentException as e: + return f"Error: {str(e)}" + except AssertionError as e: + return f"Error: {str(e)}" + except Exception as e: + return f"Error: {str(e)}" + finally: + driver.quit() + + +@pytest.mark.parametrize("course_id, course_name, ta_name", [(1, "Teecloudy - Ashly Course Testing", "Mark")]) +def test_assign_the_same_ta_to_the_same_course_twice(course_id, course_name, ta_name, driver, url, admin_username, admin_password, restore_database): + try: + driver.maximize_window() + login(driver, url, admin_username, admin_password) + + admin_tab = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "admin_tab")) + ) + admin_tab.click() + + # Hui: assign the TA for the first time + # (1) Locate the form and select the TA + ta_form = WebDriverWait(driver, 15).until( + EC.presence_of_element_located((By.XPATH, + f"//form[@id='drop_menu_form_{course_id}']")) ) + + ta_dropdown = Select(ta_form.find_element(By.XPATH, ".//select[@name='ta']")) + ta_dropdown.select_by_visible_text(ta_name) + + # (2) Submit the form using JavaScript + driver.execute_script("arguments[0].submit();", ta_form) + + # (3) Find table courses + table_courses_before = WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.XPATH, ".//*[@id='tab-existing-courses']/table")) + ) + # (4) Find the row with matching course_name + course_row_before = table_courses_before.find_element(By.XPATH, f".//tr[td='{course_name}']") + # (5) Find the column with TA name + old_cell_content = course_row_before.find_element(By.XPATH, ".//td[4]").text + + + # Hui: assign the same TA again + ta_form = WebDriverWait(driver, 15).until(EC.presence_of_element_located((By.XPATH, f"//form[@id='drop_menu_form_{course_id}']"))) + ta_dropdown = Select(ta_form.find_element(By.XPATH, ".//select[@name='ta']")) + ta_dropdown.select_by_visible_text(ta_name) + driver.execute_script("arguments[0].submit();", ta_form) + + # Wait for an expected alert and accept it + WebDriverWait(driver, 10).until(EC.alert_is_present()) + alert = driver.switch_to.alert + alert_text = alert.text + alert.accept() + + # find table courses + table_courses_after = WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.XPATH, ".//*[@id='tab-existing-courses']/table")) + ) + # find the row with matching course_name + course_row_after = table_courses_after.find_element(By.XPATH, f".//tr[td='{course_name}']") + # find the column with TA name + new_cell_content = course_row_after.find_element(By.XPATH, ".//td[4]").text + + # assert the TA name in the column + assert old_cell_content == new_cell_content, f"Error: TA name in the column has changed from {old_cell_content} to {new_cell_content}" + + except NoSuchElementException as e: + return f"Error: {str(e)}" + except UnexpectedAlertPresentException as e: + return f"Error: {str(e)}" + except AssertionError as e: + return f"Error: {str(e)}" + except Exception as e: + return f"Error: {str(e)}" + finally: + driver.quit() diff --git a/test/SeleniumMpiana/test_results.txt b/test/SeleniumMpiana/test_results.txt new file mode 100644 index 0000000..4f55332 --- /dev/null +++ b/test/SeleniumMpiana/test_results.txt @@ -0,0 +1,2 @@ +Test Case 1 - Teecloudy - Ashly Course Testing -- JAMES: Result=Passed, Expected Result=Ta assigned successfully., Actual Alert Text=TA assigned successfully. +Test Case 2 - P.M2019 - Project Management -- DIEGO: Result=Passed, Expected Result=The selected TA is already assigned to this course., Actual Alert Text=The selected TA is already assigned to this course. diff --git a/test/SeleniumNeil/test_bug352_neil.py b/test/SeleniumNeil/test_bug352_neil.py new file mode 100644 index 0000000..283998c --- /dev/null +++ b/test/SeleniumNeil/test_bug352_neil.py @@ -0,0 +1,201 @@ +# Each time you run the test script reset the database. +# For this test script you won't need it since it changes +# the Ta's email and name automatically +import re +import time +import pytest + +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC + + +def test_restore_database(restore_database): + assert restore_database is None + + +def createTA(driver, TA_name, emails, password): + full_name = driver.find_element('name', 'fullname') + full_name.send_keys(TA_name) + email = driver.find_element('name', 'email') + email.send_keys(emails) + pas = driver.find_element('name', 'password') + pas.send_keys(password) + usr_type = driver.find_element('name', 'type') + usr_type.click() + click_create = driver.find_element('name', 'create_btn') + click_create.click() + + +def login_lecturer(driver, url): + # Open the website + driver.get(url) + driver.maximize_window() + + username_input = driver.find_element('name', "user") + + password_input = driver.find_element('name', "password") + + login_button = driver.find_element('id', "login_btn") + + # login as a Lecturer + username_input.send_keys("admin@qq.com") + password_input.send_keys("123") + # Click the login button + time.sleep(2) + login_button.click() + admin_tab = driver.find_element('id', 'admin_tab') + admin_tab.click() + + cte_instructor = driver.find_element('id', 'tab_ins_accounts') + cte_instructor.click() + time.sleep(2) + + +def test_createTA(driver, url): + driver_open = driver + driver_open.maximize_window() + login_lecturer(driver_open, url) + try: + fullname = "lanhuitest1" + email = "lanhuitest1@qq.com" + password = "new1452345678" + createTA(driver_open, fullname, email,password) # CREATE A TA WITH FULLNAME lanhuitest1 email lanhuitest1@qq.com password new1452345678 + + get_output = WebDriverWait(driver_open, 20).until( + EC.element_to_be_clickable((By.ID, "tab_ins_accounts")) + ) + get_output.click() + get_output_msg = driver_open.find_element(By.CLASS_NAME, "alert-warning") + txt_alert = get_output_msg.text + time.sleep(2) + + if txt_alert.find("TA user created successfully") == 0: + logout_button = WebDriverWait(driver_open, 20).until( + EC.element_to_be_clickable( + (By.XPATH, "//a[contains(@class, 'nav-link') and contains(@href, 'logout.php')]")) + ) + time.sleep(2) + logout_button.click() + time.sleep(2) + username_input = driver_open.find_element('name', "user") + password_input = driver_open.find_element('name', "password") + login_button = driver_open.find_element('id', "login_btn") + # login as the new TA + username_input.send_keys(email) # login with credentials of the created TA + password_input.send_keys(password) + # Click the login button + time.sleep(2) + + login_button.click() + + time.sleep(2) + elif txt_alert.find("Email address ") == 0: + + time.sleep(2) + driver_open.quit() + + else: + driver_open.quit() + + time.sleep(2) + + finally: + driver_open.quit() + + +def test_generate_password(driver, url): + driver_open = driver + login_lecturer(driver_open, url) + try: + fullname = "lanhuitest2" + email = "lanhuitest2@qq.com" + password = "" + createTA(driver_open, fullname, email, + password) # CREATE A TA WITH FULLNAME lanhuitest2 email lanhuitest2@qq.com password "" + + get_output = WebDriverWait(driver_open, 20).until( + EC.element_to_be_clickable((By.ID, "tab_ins_accounts")) + ) + get_output.click() + get_output_msg = driver_open.find_element(By.CLASS_NAME, "alert-warning") + txt_alert = get_output_msg.text + time.sleep(2) + + if txt_alert.find("TA user created successfully") == 0: + time.sleep(2) + email_pattern = r"Use email (\S+) as account name" + password_pattern = r" (\S+)\ as password." + email_match = re.search(email_pattern, txt_alert) + password_match = re.search(password_pattern, txt_alert) + if email_match and password_match: + # Extract email and password from the matches + email = email_match.group(1) + password = password_match.group(1) + logout_button = WebDriverWait(driver_open, 20).until( + EC.element_to_be_clickable( + (By.XPATH, "//a[contains(@class, 'nav-link') and contains(@href, 'logout.php')]")) + ) + logout_button.click() + time.sleep(2) + username_input = driver_open.find_element('name', "user") + password_input = driver_open.find_element('name', "password") + login_button = driver_open.find_element('id', "login_btn") + # login as the new TA + username_input.send_keys(email) # login with credentials of the created TA + password_input.send_keys(password) + # Click the login button + time.sleep(2) + + login_button.click() + + time.sleep(2) + + elif txt_alert.find("Email address ") == 0: + time.sleep(2) + driver_open.quit() + + else: + driver_open.quit() + + time.sleep(2) + + finally: + driver_open.quit() + + +def test_existingTA(driver, url, restore_database): + driver_open = driver + login_lecturer(driver, url) + try: +# Use email nreyes@example.com as account name and new1452345678 as password. + fullname = "lanhuitest1" + email = "lanhuitest1@qq.com" + password = "new1452345678" + createTA(driver_open, fullname, email, + password) # CREATE A TA WITH FULLNAME lanhuitest1 email lanhuitest1@qq.com password new1452345678 + + get_output = WebDriverWait(driver_open, 20).until( + EC.element_to_be_clickable((By.ID, "tab_ins_accounts")) + ) + get_output.click() + get_output_msg = driver_open.find_element(By.CLASS_NAME, "alert-warning") + txt_alert = get_output_msg.text + time.sleep(2) + + if txt_alert.find("TA user created successfully") == 0: + time.sleep(2) + + + elif txt_alert.find("Email address ") == 0: + time.sleep(2) + driver_open.quit() + + else: + driver_open.quit() + + time.sleep(2) + + finally: + driver_open.quit() diff --git a/test/SeleniumSylvester/test_ScriptbugSylvester.py b/test/SeleniumSylvester/test_ScriptbugSylvester.py new file mode 100644 index 0000000..fe33fab --- /dev/null +++ b/test/SeleniumSylvester/test_ScriptbugSylvester.py @@ -0,0 +1,77 @@ +import pytest +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import NoSuchElementException, TimeoutException +import time +import traceback + +driver = webdriver.Chrome() + +try: + # Navigate to the page with tabs + driver.get("http://localhost:8080/lrr/") + driver.maximize_window() + wait = WebDriverWait(driver, 10) + + # Login as a Lecturer + username_input = wait.until(EC.presence_of_element_located((By.NAME, "user"))) + password_input = driver.find_element(By.NAME, "password") + login_button = driver.find_element(By.ID, "login_btn") + + username_input.send_keys("ashly@qq.com") + password_input.send_keys("admin123") + time.sleep(5) + login_button.click() + + course_but= driver.find_element(By.XPATH, "(//div[@class='btn btn-default'])[1]") # Adjust this XPATH as needed + + + # Click on the alert + course_but.click() + time.sleep(5) + + marked_tab = wait.until( + EC.element_to_be_clickable((By.XPATH, "//a[text()='Marked']")) + ) + marked_tab.click() + + # Wait for the Marked tab content to be present + marked_tab_content = wait.until( + EC.presence_of_element_located((By.XPATH, "//div[@id='menu4' and contains(@class, 'active')]")) + ) + + time.sleep(5) + remark_but = wait.until( + EC.presence_of_element_located((By.XPATH, "//button[normalize-space()='Request remarking']")) + ) + remark_but.click() + + + time.sleep(2) + + # Switch to the alert + alert = driver.switch_to.alert + + # Send keys to the prompt + alert.send_keys("Number 2 was correct") + + # Accept the prompt (click OK) + alert.accept() + + time.sleep(5) + + + +except NoSuchElementException as e: + print("NoSuchElementException: Could not find an element.") + traceback.print_exc() +except TimeoutException as e: + print("TimeoutException: An element took too long to load.") + traceback.print_exc() +except Exception as e: + print(f"An unexpected error occurred: {e}") + traceback.print_exc() +finally: + driver.quit() diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..3741f44 --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,46 @@ +import os +import pytest +from selenium import webdriver + + +@pytest.fixture +def restore_database(): + ''' Restore the database. + It is useful for making sure that each end-to-end test + starts with the same database. + Benefit: we can reproduce the same test result. + ''' + + PASSWORD = 'p-@va9' # root password + DB_NAME = 'lrr' # database name used for LRR + + # commands used to import data to DB_NAME + cmds = [ + f'mysql -u root -p{PASSWORD} -e "DROP DATABASE IF EXISTS {DB_NAME};"', + f'mysql -u root -p{PASSWORD} -e "CREATE DATABASE {DB_NAME};"', + f'mysql -u root -p{PASSWORD} -e "GRANT ALL PRIVILEGES ON {DB_NAME}.* TO lrr@localhost WITH GRANT OPTION;"', + f'mysql -u root -p{PASSWORD} {DB_NAME} < ../lrr_database.sql'] + + for command in cmds: + os.system(command) + return None + + +@pytest.fixture +def url(): + return 'http://localhost/LRR/' # URL of LRR + + +@pytest.fixture +def driver(): + return webdriver.Chrome() + + +@pytest.fixture +def admin_username(): + return 'admin@qq.com' + + +@pytest.fixture +def admin_password(): + return '123'