#include #include /* for free() */ #include #include #include #include #include #include "global.h" #include "custom.h" SESSION user; CONFIG_STRUCT conf; /* the configuration read from config file */ #include "manhat-lib/shared_util.h" #include "manhat-lib/shared_lock.h" #include "manhat-lib/shared_cs_util.h" #include "manhat-lib/shared_hotpot_parser.h" #include "manhat-lib/shared_news_util.h" #include "manhat-lib/shared_hotpot_exam_util.h" #include "manhat-lib/shared_strtrm.h" #include "manhat-lib/shared_file_util.h" #include "manhat-lib/shared_server_string.h" #include "manhat-lib/shared_bb_manifest.h" HDF *hdf; static void read_parameters (char *course, char *key, char *sender, char *fname, char *info_fname, time_t *start_time, long *offset) { char tmp[MAX_PATH + 1]; cs_get_required_parameter ("crs", course, MAX_FILENAME); /* shared_cgi_util.c */ cs_get_required_parameter ("id", key, MAX_KEY); cs_get_required_parameter ("sender", sender, MAX_USERNAME); cs_get_required_parameter ("inf", info_fname, MAX_FILENAME); cs_get_required_parameter ("test", tmp, MAX_PATH); *start_time = atol(tmp); cs_get_required_parameter("loc", tmp, MAX_PATH); *offset = atol(tmp); /* note we do NOT free the entries list here! **/ } /** removes leading and trailing spaces from string *** Also replaces multiple occurrences of whitespace characters within *** the string with a single space. EG: *** the string " This is a string with spaces " *** becomes: *** "This is a string with spaces" **/ static void despace_string(char *string) { char *newstring; char *ptr; int space_found; int i; if(strlen(string)) /* do nothing unless the string has length */ { strtrm(string); /* shared_util.c - remove leading, trailing whitespace */ if(strlen(string)) /* is anything left? */ { newstring = (char *) malloc( (strlen(string) + 1) * sizeof(char)); if(!newstring) cs_critical_error(ERR_MALLOC_FAILED, ""); for(ptr=string, space_found = 0, i = 0; *ptr; ptr++) /* for each char in the string */ { if(isspace(*ptr)) { if(!space_found) /* if the last char processed was NOT a whitespace character */ { *(newstring + i) = ' '; /* store a single space */ i++; space_found = 1; /* set the flag */ } } else /* we're not dealing with a whitespace character */ { *(newstring + i) = *ptr; /* store the normal character */ i++; space_found = 0; /* unset the flag */ } } /* for(...) */ *(newstring + i) = '\0'; /* null terminate the string */ strcpy(string,newstring); /* return the result */ free(newstring); } /* if strlen(string) */ } /* if strlen(string) */ } char * get_response(char *string) { char *response; int len = strlen(string); response = (char *) malloc(sizeof(char) * len +1); /* allocate just one byte */ if(!response) cs_critical_error(ERR_MALLOC_FAILED, ""); strncpy(response, string, len +1); *(response + len) = '\0'; return response; } /** Note that this does not free memory allocated by calls to cgi_val_multi() *** This should eventually be fixed **/ static void store_responses() { #define MAX_TMP_NAME 30 QUESTION_NODE *q_ptr; int q; char *value; int j; char varname[MAX_TMP_NAME]; char *buf; for(q=0, q_ptr=quiz_data.head; q_ptr; q++, q_ptr=q_ptr -> next) { if(q_ptr->question_type == MC) { snprintf(varname, MAX_TMP_NAME, "Query.q%d",q); /* build the variable name as delivered by the form */ value = hdf_get_value(global_cgi->hdf, varname, ""); despace_string(value); q_ptr->response = get_response(value); } else if(q_ptr->question_type == SHORT) { snprintf(varname, MAX_TMP_NAME, "Query.q%d",q); /* build the variable name as delivered by the form */ value = hdf_get_value(global_cgi->hdf, varname, ""); despace_string(value); q_ptr->response = get_response(value); } else if(q_ptr->question_type == MC_MULTI) { snprintf(varname, MAX_TMP_NAME, "Query.q%d.0",q); /* build the variable name as delivered by the form */ value = hdf_get_value(global_cgi->hdf, varname, 0); if(value) { buf = (char*)malloc(sizeof(char) * MAX_HOTPOT_CHOICES + 1); if(!buf) cs_critical_error(ERR_MALLOC_FAILED, ""); *buf = '\0'; for(j =0; j < q_ptr->answer_count; j++) { snprintf(varname, MAX_TMP_NAME, "Query.q%d.%d",q, j); /* build the variable name as delivered by the form */ value = hdf_get_value(global_cgi->hdf, varname, 0); if(value) { despace_string(value); strcat(buf, value); } } q_ptr->response = get_response(buf); if(buf) free(buf); } else { snprintf(varname, MAX_TMP_NAME, "Query.q%d",q); /* build the variable name as delivered by the form */ value = hdf_get_value(global_cgi->hdf, varname, 0); despace_string(value); q_ptr->response = get_response(value); } } } #undef MAX_TMP_NAME } /* int get_unique_fd(const char *base_dir, const char *filetype, char *unique_fname, time_t *time_sent) { char template[MAX_PATH + 1]; char timestring[MAX_TIMESTRING + 1]; int fd; char *ptr; strftime (timestring, MAX_TIMESTRING, "%m%d%Y%H%M%S",localtime (time_sent)); snprintf (template, MAX_PATH + 1, "%s%s_%s_XXXXXX", base_dir,filetype, timestring); fd = mkstemp(template); if(fd == -1) cs_critical_error(ERR_MKSTEMP_FAILED, ""); change_mkstemp_permission(template); ptr = strrchr(template,'/'); if(!ptr) cs_critical_error(ERR_GENERAL_ERROR, ""); ptr++; strncpy(unique_fname, ptr,MAX_FILENAME + 1); if(fchmod(fd,S_IWGRP|S_IRGRP|S_IRUSR|S_IWUSR)) cs_critical_error(ERR_CHMOD_FAILED,""); return fd; } */ /* note that this creates the file */ void get_unique_fname(char *unique_fname, const char *base_dir, const char *filetype, time_t *time_sent) { int fd; fd = get_unique_fd(base_dir, filetype, unique_fname, time_sent); close(fd); } static int jqz_correct_answer(const char *response, const char *answer) { char *new_answer; int result = 0; if(strlen(response)) { if(strlen(answer)) { new_answer= (char *) malloc((strlen(answer) + 1) * sizeof(char)); if(!new_answer) cs_critical_error(ERR_MALLOC_FAILED, ""); strcpy(new_answer,answer); despace_string(new_answer); if(quiz_data.case_sensitive) result = !strcmp(response,new_answer); else result = !strcasecmp(response, new_answer); free(new_answer); } } return result; } static int short_question_correct(QUESTION_NODE *q_ptr) { ANSWER_NODE *a_ptr; int correct_found; for(a_ptr = q_ptr->head, correct_found = 0; a_ptr && !correct_found; a_ptr=a_ptr->next) correct_found = (a_ptr->is_correct) && jqz_correct_answer(q_ptr->response, a_ptr->answer); if(correct_found) return 1; else return 0; } static int mc_question_correct(QUESTION_NODE *qptr) { int correct = 0; if(qptr->correct_answer_string && *(qptr->correct_answer_string)) correct = qptr->response && *(qptr->response) && (int)strchr(qptr->correct_answer_string, *(qptr->response)); return correct? 1 : 0; } static int mc_multi_question_correct(QUESTION_NODE *qptr) { char *ptr; if(qptr->correct_answer_string && *(qptr->correct_answer_string)) { /* every char of the person's response must be in the correct answers string */ for(ptr=qptr->response; *ptr; ptr++) if(!strchr(qptr->correct_answer_string,*ptr)) return 0; for(ptr=qptr->correct_answer_string; *ptr; ptr++) if(!strchr(qptr->response,*ptr)) return 0; return 1; } return 0; /* no answers are correct */ } static void get_totals(int *total_questions, int *total_correct) { QUESTION_NODE *q_ptr; *total_correct = 0; for(q_ptr = quiz_data.head, *total_questions=0; q_ptr; (*total_questions)++, q_ptr=q_ptr->next) { switch(q_ptr->question_type) { case MC: q_ptr->answer_correct = mc_question_correct(q_ptr); break; case SHORT: q_ptr->answer_correct = short_question_correct(q_ptr); break; case MC_MULTI: q_ptr->answer_correct = mc_multi_question_correct(q_ptr); break; default: cs_critical_error(ERR_UNKNOWN_QUESTION_TYPE, ""); break; } if(q_ptr->answer_correct) *total_correct = *total_correct + 1; } } static void get_elapsed_timestring(time_t start_time, time_t end_time, char *elapsed_timestring) { time_t diff; diff = end_time - start_time; strftime(elapsed_timestring,MAX_TIMESTRING, "%H:%M:%S", gmtime(&diff)); } static void print_return_to_assign_form( char *course, char *key, long offset) { char temp[30]; cs_set_value("crs", course); cs_set_value("id", key); snprintf(temp, 30, "%ld", offset); cs_set_value("loc", temp); snprintf(temp, 30, "%d", ASSIGNMENTS); cs_set_value("grp", temp); } static void show_success ( char *course, char *key, long offset) { print_return_to_assign_form(course, key, offset); } void print_style(FILE *fp) { fprintf(fp,"\n"); } static void do_html_header(FILE *fp, const char *symlink_fname) { char server_string[MAX_PATH + 1]; get_server_string(server_string, MAX_PATH + 1); /* shared_server_string.c */ fprintf(fp, "\n\n"); fprintf(fp, "%s\n",quiz_data.title?quiz_data.title: hdf_get_value(hdf, "hotpot.page_title", "")); print_style(fp); if(*symlink_fname) fprintf(fp, "\n\n", server_string, IMAGE_PATH, symlink_fname); fprintf(fp, "\n\n\n"); } static void do_html_summary(FILE *fp, time_t start_time, time_t end_time, const char *elapsed_timestring, int total_correct, int total_questions) { char timestring[MAX_TIMESTRING + 1]; fprintf(fp,"\n"); fprintf(fp, "\n", hdf_get_value(hdf, "hotpot.results","")); /* print student info */ fprintf(fp, "\n", hdf_get_value(hdf, "hotpot.student_label",""), user.realname, user.username); /* print start time */ strftime (timestring, MAX_TIMESTRING, DOW_DATE_TIME_FORMAT, localtime (&(start_time))); fprintf(fp, "\n", hdf_get_value(hdf, "hotpot.start_time",""), timestring); /* print end time */ strftime (timestring, MAX_TIMESTRING, DOW_DATE_TIME_FORMAT, localtime (&(end_time))); fprintf(fp, "\n", hdf_get_value(hdf, "hotpot.end_time",""), timestring); /* print elapsed time */ fprintf(fp, "\n", hdf_get_value(hdf, "hotpot.elapsed_time",""), elapsed_timestring); /* print score */ fprintf(fp, "\n", hdf_get_value(hdf, "hotpot.score",""), total_correct, total_questions); fprintf(fp,"
%s
%s%s (%s)
%s%s
%s%s
%s%s
%s%d/%d
\n"); } static void print_html_string(FILE *fp, char *string) { char *ptr; int nl = 0; for(ptr=string;*ptr; ptr++) { if(*ptr == '\n') { if(nl) fputs("

",fp); // else // fputs("
",fp); nl = 1; } else { fputc(*ptr,fp); nl = 0; } } } static void do_html_instructions(FILE *fp) { if(quiz_data.title) { fprintf(fp,"

%s

\n",quiz_data.title); } if(quiz_data.sub_title) { print_html_string(fp,quiz_data.sub_title); } if(quiz_data.instructions) { fputs("

",fp); print_html_string(fp, quiz_data.instructions); fputs("

\n
",fp); } } static void do_html_mc_answers(FILE *fp, QUESTION_NODE *q_ptr) { ANSWER_NODE *a_ptr; int a; /* print the answers in a table */ fputs("\n",fp); for(a_ptr = q_ptr->head, a=0; a_ptr; a_ptr = a_ptr->next, a++) { fputs("",fp); /* one row per answer */ /* print "your answer -->" in first column, if appropriate */ // fputs("",fp); /* print the answer text in the second column */ fprintf(fp,"\n",fp); /* if this answer was a correct answer (according to the answer key), ** put "<--- Correct answer " in third column */ fputs("\n",fp); } /* print correct, incorrect, etc */ fprintf(fp,"",fp); fputs("
",fp); if(q_ptr->response && *(q_ptr->response) && strchr(q_ptr->response, hotpot_answer_letters[a])) /* if student selected this item */ fprintf(fp, "%s",hdf_get_value(hdf, "hotpot.your_answer_label", "")); else fputs("",fp); fputs("%c",hotpot_answer_letters[a]); print_html_string(fp,a_ptr->answer); fputs("",fp); if(a_ptr->is_correct) /* if answer is correct */ fprintf(fp,"%s", hdf_get_value(hdf, "hotpot.correct_answer","")); fputs("
\n"); if(!*(q_ptr->response)) fprintf(fp,"%s", hdf_get_value(hdf, "hotpot.no_answer","")); else if(q_ptr->answer_correct) fprintf(fp,"%s", hdf_get_value(hdf, "hotpot.correct", "")); else fprintf(fp,"%s", hdf_get_value(hdf, "hotpot.wrong", "")); fputs("
",fp); } static void do_html_short_answers(FILE *fp, QUESTION_NODE *q_ptr) { ANSWER_NODE *a_ptr; fputs("",fp); /* print student's answer in first row */ fprintf(fp,"\n", hdf_get_value(hdf, "hotpot.your_answer_label",""), q_ptr->response); if(q_ptr->answer_correct) fprintf(fp,"\n", hdf_get_value(hdf, "hotpot.correct","")); else { /* print all the correct answers */ for(a_ptr = q_ptr->head; a_ptr; a_ptr=a_ptr->next) if(a_ptr->is_correct) fprintf(fp,"\n", a_ptr->answer,hdf_get_value(hdf, "hotpot.correct_answer","")); fprintf(fp,"\n", hdf_get_value(hdf, "hotpot.wrong","")); } fputs("
%s%s
%s
%s%s
%s
\n",fp); } static void do_html_questions(FILE *fp) { int q; QUESTION_NODE *q_ptr; for(q_ptr = quiz_data.head, q=0 ; q_ptr; q_ptr=q_ptr->next, q++) { /* print the actual question */ fprintf(fp, "

%s%d: ", hdf_get_value(hdf, "hotpot.q", "Qii"),q+1); print_html_string(fp, q_ptr->question); fputs("
",fp); switch(q_ptr->question_type) { case MC_MULTI: case MC: do_html_mc_answers(fp, q_ptr); break; case SHORT: do_html_short_answers(fp, q_ptr); break; default: cs_critical_error(ERR_UNKNOWN_QUESTION_TYPE, ""); break; } } } static void do_html_footer(FILE *fp) { //fprintf(fp, "\n"); fprintf(fp, "\n\n"); } static void write_result_html(FILE *fp, time_t start_time, time_t end_time, int total_questions, int total_correct, const char *elapsed_timestring, const char *symlink_fname) { do_html_header(fp, symlink_fname); do_html_summary(fp, start_time, end_time, elapsed_timestring, total_correct, total_questions); do_html_instructions(fp); do_html_questions(fp); do_html_footer(fp); } static void write_website(char *unique_fname, char *orig_fname, time_t time_sent, const char *assign_sender, const char *dirname, time_t start_time, int total_questions, int total_correct, const char *elapsed_timestring) { char new_website_path[MAX_PATH + 1]; char orig_exam_path[MAX_PATH + 1]; char manifest[MAX_PATH + 1]; char *base = 0; struct stat buffer; char *cmd; FILE *fp; /* get a unique directory name to hold the website */ get_hotpot_unique_fname (unique_fname, discussion_path,NORMAL_WEBSITE_DIR_PREFIX, &time_sent); /* shared_hotpot_exam_util.c */ /* build full path to the new directory */ snprintf (new_website_path, MAX_PATH + 1, "%s%s", discussion_path,unique_fname); unlink(new_website_path); /*get_hotpot_unique_fname created a regular file. We need to delete it */ /* build full path to the teacher's directory that holds the original exam */ snprintf (orig_exam_path, MAX_PATH + 1, "%s%s/%s/%s/%s", conf.course_path, PEOPLE_DIR, assign_sender, ASSIGNMENTS_DIR, dirname); /* use the cp -a shell command to make a complete copy of the original dir */ cmd = (char *) malloc( (strlen(new_website_path) + strlen(orig_exam_path) + 20) * sizeof(char)); if(!cmd) cs_critical_error(ERR_MALLOC_FAILED, ""); sprintf(cmd, "cp -pR %s %s", orig_exam_path, new_website_path); if(system(cmd)) cs_critical_error(ERR_GENERAL_ERROR, ""); free(cmd); snprintf(orig_fname,MAX_FILENAME + 1, "%s_%ld.html", user.username, (long)time_sent); snprintf(manifest, MAX_PATH +1, "%s/%s", new_website_path, "imsmanifest.xml"); if( !stat(manifest, &buffer)) base = get_bb_image_base(manifest); /* shared_bb_manifest.c */ if(base) { /* Blackboard 6 exams will not actually have a base directory if there are ** no images in the exam, so we need to make sure the directory is actually ** there */ if(!isdirectory(new_website_path, base)) /* shared_file_util.c */ { strcat(new_website_path,"/"); strcat(new_website_path, base); if(mkdir(new_website_path, 0770)) cs_critical_error(ERR_MKDIR_FAILED, new_website_path); } else { strcat(new_website_path,"/"); strcat(new_website_path, base); } free(base); } strcat(new_website_path,"/"); strcat(new_website_path, orig_fname); fp = fopen(new_website_path, "w"); if(!fp) cs_critical_error(ERR_FOPEN_WRITE_FAILED, new_website_path); write_result_html(fp, start_time, time_sent, total_questions, total_correct, elapsed_timestring,""); fclose(fp); } static void write_msg_file(const char *sender, time_t start_time, time_t end_time, char *msg_fname, char *subject_line, int total_questions, int total_correct, const char *elapsed_timestring) { char filepath[MAX_PATH + 1]; FILE *fp; char timestring[MAX_TIMESTRING + 1]; get_unique_fname(msg_fname, discussion_path, "msg", &end_time); snprintf (filepath, MAX_PATH + 1, "%s/%s", discussion_path, msg_fname); fp = fopen(filepath,"w"); if(!fp) cs_critical_error(ERR_FOPEN_WRITE_FAILED, ""); snprintf(subject_line, MAX_SUBJECT + 1, "%d/%d %s %s", total_correct, total_questions, hdf_get_value(hdf, "hotpot.in", ""), elapsed_timestring); fprintf(fp, "%s\n\n", hdf_get_value(hdf, "hotpot.results", "")); /* print student info */ fprintf(fp, "%s %s (%s)\n", hdf_get_value(hdf, "hotpot.student_label", ""), user.realname, user.username); /* print start time */ strftime (timestring, MAX_TIMESTRING, DOW_DATE_TIME_FORMAT, localtime (&(start_time))); fprintf(fp, "%s %s\n", hdf_get_value(hdf, "hotpot.start_time",""), timestring); /* print end time */ strftime (timestring, MAX_TIMESTRING, DOW_DATE_TIME_FORMAT, localtime (&(end_time))); fprintf(fp, "%s %s\n", hdf_get_value(hdf, "hotpot.end_time", ""), timestring); /* print elapsed time */ fprintf(fp, "%s %s\n", hdf_get_value(hdf, "hotpot.elapsed_time", ""), elapsed_timestring); /* print score */ fprintf(fp, "%s %d/%d\n", hdf_get_value(hdf, "hotpot.score", ""), total_correct, total_questions); fclose(fp); } /** dirname is the name of the directory, within assign_sender's assign directory, which holds the exam **/ static void write_graded_quiz( char *course, char *key, const char *assign_sender, long offset, int total_questions, int total_correct, time_t start_time, time_t end_time, const char *elapsed_timestring, const char *dirname) { FILE *inbox_fp; int inbox_fd; int info_fd; ATTACHMENT web_attachment; char subject[MAX_SUBJECT + 1]; char msg_fname[MAX_FILENAME + 1]; char info_fname[MAX_FILENAME + 1]; char inbox_fname[MAX_PATH + 1]; CENTRAL_INBOX_RECORD inbox_record, new_inbox_record; write_msg_file(assign_sender, start_time, end_time, msg_fname, subject, total_questions, total_correct, elapsed_timestring); write_website(web_attachment.unique_fname, web_attachment.orig_fname, end_time, assign_sender, dirname, start_time, total_questions, total_correct, elapsed_timestring); /* set up as much of the new inbox record as we can before locking ** the inbox.dat file */ strncpy(new_inbox_record.info.msg_fname, msg_fname, MAX_FILENAME + 1); strncpy(new_inbox_record.info.sender, user.username, MAX_USERNAME + 1); strncpy(new_inbox_record.info.sender_realname, hdf_get_value(hdf, "hotpot.handler", ""), MAX_NAME + 1); strncpy(new_inbox_record.info.receiver_username,user.username,MAX_USERNAME + 1); /* since this is a student sending the message */ strncpy(new_inbox_record.info.subject, subject, MAX_SUBJECT + 1); new_inbox_record.info.time_sent = end_time; new_inbox_record.info.attachments = -1; /* since there's a website attached */ new_inbox_record.info.sender_team = user.team; /* get the name of the 'inf' file to do so, we need to open ** (create) the file */ info_fd = get_unique_fd (discussion_path,"inf", info_fname, &end_time); /* don't bother to lock it, since no one knows it's there yet! */ strncpy(new_inbox_record.info_fname,info_fname,MAX_FILENAME + 1); new_inbox_record.release_time = 0; /* no release time set */ new_inbox_record.unrelease_time = 0; /* no unrelease time either */ new_inbox_record.status = TEACHER_HIDDEN; /* since we don't want the student to see the results! */ /* now open and lock the inbox.dat file */ snprintf(inbox_fname,MAX_PATH + 1,"%s%s",central_discussion_path,INBOX_FNAME); inbox_fp = fopen(inbox_fname,"r+"); if(!inbox_fp) /* couldn't open or create inbox.dat */ cs_critical_error(ERR_FOPEN_WRITE_FAILED,""); inbox_fd = fileno(inbox_fp); get_exclusive_lock(inbox_fd, 1); /* shared_lock.c */ /* now seek to the message that held the original exam, so we can get the topic_id */ if(fseek(inbox_fp,offset,SEEK_SET) == -1) cs_critical_error(ERR_FSEEK_FAILED, ""); if(fread( &inbox_record, sizeof(CENTRAL_INBOX_RECORD), 1, inbox_fp) != 1) cs_critical_error(ERR_FREAD_FAILED, ""); /* this msg is a reply to the original assignment */ new_inbox_record.info.reply_to = inbox_record.info.msg_id; new_inbox_record.info.topic_id = inbox_record.info.msg_id; /* find the msg_id for this message, by seeking to the last * message and incrementing */ if(fseek(inbox_fp,-sizeof(CENTRAL_INBOX_RECORD),SEEK_END) == -1) cs_critical_error(ERR_FSEEK_FAILED, ""); if(fread( &inbox_record, sizeof(CENTRAL_INBOX_RECORD), 1, inbox_fp) != 1) cs_critical_error(ERR_FREAD_FAILED, ""); new_inbox_record.info.msg_id = inbox_record.info.msg_id + 1; /* we have all the data we need, so we append the record to the ** end of the central inbox */ if(fseek(inbox_fp,0,SEEK_END) == -1) cs_critical_error(ERR_FSEEK_FAILED, ""); if(fwrite(&new_inbox_record, sizeof(CENTRAL_INBOX_RECORD),1,inbox_fp) != 1) cs_critical_error(ERR_FWRITE_FAILED, ""); /* write the info file */ if (write (info_fd, &new_inbox_record.info, sizeof (NEWS_INFO)) != sizeof(NEWS_INFO)) cs_critical_error (ERR_FWRITE_FAILED, ""); if(write(info_fd, &web_attachment, sizeof(ATTACHMENT)) != sizeof(ATTACHMENT)) cs_critical_error (ERR_FWRITE_FAILED, ""); close (info_fd); release_lock(inbox_fd); /* shared_lock.c */ fclose(inbox_fp); show_success(course,key,offset); } /** returns the string orig_response, *** with commas converted to semi-colons and double-quotes ** converted to single quotes **/ static char * cleaned_up_response(const char *orig_response) { #define SINGLE_QUOTE 39 char *cleaned_response; char *ptr; if(!orig_response) /* check for NULL pointer */ { cleaned_response = (char *) malloc(sizeof(char)); if(!cleaned_response) cs_critical_error(ERR_MALLOC_FAILED, ""); *cleaned_response = '\0'; } else { cleaned_response = (char *) malloc((strlen(orig_response) + 1) * sizeof(char)); if(!cleaned_response) cs_critical_error(ERR_MALLOC_FAILED, ""); strcpy(cleaned_response, orig_response); for(ptr = cleaned_response; *ptr; ptr++) { if(*ptr == '"') *ptr = SINGLE_QUOTE; if(*ptr == ',') *ptr = ';'; } } return cleaned_response; #undef SINGLE_QUOTE } static void store_exam_results(const char *username, const char *course_path, const char *sender, const char *info_fname, time_t start_time, time_t end_time, const char *elapsed_timestring, int total_questions, int total_correct) { char data_path[MAX_PATH + 1]; FILE *fp; int fd; QUESTION_NODE *q_ptr; char *cleaned_response = 0; get_hotpot_score_path(course_path, username, sender, info_fname, data_path); /* shared_hotpot_exam_util.c */ fp = fopen(data_path,"a"); if(!fp) cs_critical_error(ERR_FOPEN_APPEND_FAILED,""); fd = fileno(fp); get_exclusive_lock(fd,1); /* shared_lock.c */ fprintf(fp,"%d,%d,\"%s\",%ld,%ld,", total_correct,total_questions,elapsed_timestring,(long)start_time, (long)end_time); for(q_ptr=quiz_data.head; q_ptr; q_ptr=q_ptr -> next) { cleaned_response = cleaned_up_response(q_ptr->response); fprintf(fp,"%s%s", cleaned_response, q_ptr->next?",":""); if(cleaned_response) { free(cleaned_response); cleaned_response = 0; } } fprintf(fp,"\n"); release_lock(fd); fclose(fp); } static void report_already_scored_error( char *course, char *key, long offset) { print_return_to_assign_form(course, key, offset); cs_set_int_value("already_scored", 1); cs_set_course_info(&conf); cs_set_current_time(); cs_cgi_display("hotpot_exam_score", 1); cs_cgi_destroy(); exit(0); } /** the form submitted that contains the exam has a *** 'hidden' parameter 'test' that contains the *** start time for the exam. That value is used only *** for teachers (since their data is not stored anyway) *** The true start_time for an exam is stored at the *** beginning of the student's *.score datafile. *** That start_time was written there by the 'present' program. *** The next function retrieves that start_time. *** *** While doing so, it also checks to make sure that results for *** an exam with this start_time have not already been stored. *** This is necessary to prevent students from pressing the Back *** button on their browser and submitting the exam again. **/ static void verify_start_time(time_t *start_time, const char *course_path, const char *username, const char *sender, const char *info_fname, char *course, char *key, long offset) { #define MAX_LINE 1000 FILE *fp; int fd; char data_path[MAX_PATH + 1]; char line[MAX_LINE + 1]; char stored_time_string[MAX_TIMESTRING + 1]; char start_time_string[MAX_TIMESTRING + 1]; time_t time_viewed; get_hotpot_score_path(course_path, username, sender, info_fname, data_path); /* shared_hotpot_exam_util.c */ fp = fopen(data_path, "r+"); if(!fp) cs_critical_error(ERR_FOPEN_READ_FAILED, ""); fd = fileno(fp); get_exclusive_lock(fd,1); /* shared_lock.c */ /** the time this test was viewed by the student *** is stored as binary bytes at the start of the file **/ if(fread(&time_viewed, sizeof(time_t), 1, fp) != 1) cs_critical_error(ERR_FREAD_FAILED, ""); /** It is possible that the teacher clicked the *** "Allow retake" button WHILE THE STUDENT WAS IN *** the middle of taking the test. The "allow retake" *** button sets the initial time_viewed bytes of the file *** to zero. *** *** If we find a zero there, we have no choice but to *** trust the start_time passed to this program. We *** write that value back to the file, replacing the *** zero values. **/ if(time_viewed == 0) { if(fseek(fp, 0, SEEK_SET)) cs_critical_error(ERR_FSEEK_FAILED, ""); if( fwrite( start_time, sizeof(time_t), 1, fp ) != 1) cs_critical_error(ERR_FWRITE_FAILED,""); } else *start_time = time_viewed; /* now read the rest of the data file, checking ** to make sure that a score hasn't already been processed ** for this start_time */ /* store the time THIS test was started as a string */ snprintf(start_time_string,MAX_TIMESTRING, "%ld", (long)*start_time); while(fgets(line, MAX_LINE, fp)) /* for each exam result */ { /* the start time is the fourth item in the csv line */ if(csv_value(line,3,stored_time_string, MAX_TIMESTRING)) /* shared_hotpot_exam_util.c */ cs_critical_error(ERR_ERROR_PARSING_SCORE, ""); if(!strcmp(start_time_string, stored_time_string)) { release_lock(fd); fclose(fp); report_already_scored_error(course,key,offset); /* this exits the program */ } } release_lock(fd); fclose(fp); } int main (void) { char course[MAX_PATH + 1]; /* from crs=? command line */ char key[MAX_KEY + 1]; /* from id=? command line */ char sender[MAX_USERNAME + 1]; /* from user=? command line */ char fname[MAX_FILENAME + 1]; /* from fname=? cmd line */ char info_fname[MAX_FILENAME + 1]; /* from info=? cmd line */ long offset; /* from loc=? cmd line */ time_t start_time; /* from test=? cmd line */ char source_path[MAX_PATH + 1]; char dirname[MAX_FILENAME + 1]; char symlink_fname[MAX_FILENAME + 1]; time_t end_time; int total_questions; /* how many questions were there? */ int total_correct; /* how many questions did this user get correct? */ char elapsed_timestring[MAX_TIMESTRING + 1]; char back_url[MAX_PATH + 1]; end_time = time(NULL); cs_cgi_init(); read_parameters (course, key, sender, fname, info_fname, &start_time, &offset); read_configuration_file (course, &conf); /* shared_util.c */ validate_key (key, &user, &conf); /* shared_util.c */ remove_stale_symlinks (user.username, course, conf.access); /* shared_news_util.c */ get_hotpot_exam_source_fname (source_path, dirname, conf.course_path, sender, info_fname); /* shared_hotpot_exam_util.c */ parse_hotpot_file(source_path); /* shared_hotpot_parser.c */ store_responses(); get_lang_string(&hdf); if(user.group != FACULTY) verify_start_time(&start_time, conf.course_path, user.username, sender, info_fname, course, key, offset); get_totals(&total_questions, &total_correct); get_elapsed_timestring(start_time, end_time, elapsed_timestring); set_discussion_names(conf.course_path, user.username, ASSIGNMENTS); /* shared_news_util.c */ snprintf(back_url, MAX_PATH +1, "%s?crs=%s&id=%s&grp=6&loc=%ld", "news_read", course, key, offset); cs_set_value("back_url", back_url); if(user.group != FACULTY) { write_graded_quiz(course,key,sender, offset, total_questions, total_correct, start_time, end_time, elapsed_timestring, dirname); store_exam_results(user.username, conf.course_path, sender,info_fname,start_time, end_time, elapsed_timestring, total_questions, total_correct); cs_set_course_info(&conf); cs_set_current_time(); cs_cgi_display("hotpot_exam_score", 1); } else { create_hotpot_exam_symlink(conf.course_path, sender, dirname, &user, course, symlink_fname, conf.access); /*shared_hotpot_exam_util.c */ send_content_type(); write_result_html(stdout, start_time, end_time, total_questions, total_correct, elapsed_timestring, symlink_fname); } hdf_destroy(&hdf); free_quiz_data(); /* shared_hotpot_parser.c */ cs_cgi_destroy(); return 0; }