#include #include /* for abs() */ #include #include #include #include "global.h" #include "custom.h" /* for date FORMAT strings */ #include "manhat-lib/shared_util.h" #include "manhat-lib/shared_cs_util.h" #include "manhat-lib/shared_news_util.h" #include "manhat-lib/shared_person_list.h" /* for build_option_person_list() */ #include "manhat-lib/shared_lock.h" #include "manhat-lib/shared_translate_url.h" #include "manhat-lib/shared_access.h" #include "manhat-lib/shared_msg_line.h" #define GATHER_ALL "_all_" #define GATHER_UNREAD "_unread_" #define GATHER_FLAGGED "_flagged_" #define GATHER_LAST "_last_" typedef enum gather_type { UNREAD, FLAGGED, ALL, USERNAME, LAST } GATHER_TYPE; typedef struct gather_struct { int target_topic; /* this is read from an optional parameter sent to this CGI program if an 'topic' parameter is sent, it will contain the topic_id that needs to be sent to the "assign_fac_expand" program when the user clicks the 'Back' button. If the 'topic' parameter is not sent, this will be set to -1, and the 'Back' button will return to the NEWS_TOPIC program */ long topic_offset; /* if this is -1, then the optional 'loc' parameter was not sent to this CGI program. We will be looking at ALL of the Assignments. If this is not -1, then it means the 'loc' parameter was received by the CGI. We will be looking at only the topic referred to by this offset. */ char username[MAX_USERNAME + 1]; char realname[MAX_NAME + 1]; GATHER_TYPE target; time_t time_read; /* if GATHER_TYPE is 'UNREAD', then this contains the time the messages were read via this gather program */ } GATHER_STRUCT; /* these are declared globally to reduce overhead ** for a recursive function in this program **/ GATHER_STRUCT global_gather_criteria; /* declared globally to reduce overhead on a recursive function */ time_t global_current_time; SESSION global_user; CONFIG_STRUCT global_conf; /* the configuration read from config file */ MSG_TREE_NODE *global_topic_ptr; /* should always point to the topic (assignment) we are working with */ char global_course[MAX_PATH + 1]; /* from crs=? command line */ char global_key[MAX_KEY + 1]; /* from id=? command line */ int global_can_write; /* can this user write to this course? */ char global_topic_subject[MAX_SUBJECT + 1]; int global_topic_teacher_hidden; /* did the teacher hide the topic */ int global_topic_read_only; /* did the teacher mark the topic READ-ONLY? */ char global_username[MAX_USERNAME + 1] = ""; char global_realname[MAX_NAME + 1] = ""; int global_teacher_search; /* are we searching for msgs by an individual who is FACULTY ? */ int global_indent = 0; ATTACHMENT_NODE *global_attachment_head = (ATTACHMENT_NODE *) 0; P_NODE *global_person_head = (P_NODE *) 0; static void read_parameters (char *course, char *key, char *gather, int *del_link, int *topic, long *loc, int *printer_friendly, int *fixed_font) { char tmp[MAX_FILENAME + 1]; /* scratch variable */ cs_get_required_parameter ("crs", course, MAX_PATH); cs_get_required_parameter ("id", key, MAX_KEY); cs_get_optional_parameter ("gather", gather, MAX_PATH); if(strlen(gather) ==0) strncpy (gather, GATHER_LAST, MAX_PATH + 1); cs_get_optional_parameter("loc", tmp, MAX_FILENAME); if(!strlen(tmp)) *loc = -1; /* loc parameter not sent */ else *loc = atol(tmp); /* topic parameter is optional, but if it is provided, the 'loc' parameter must also exist. Both the 'topic' and the 'loc' parameter are sent from the assign_fac_expand program When gathering a single assignment from the NEWS_TOPIC program, (by clicking on the 'G' next to the assignment) only the loc is sent. */ cs_get_optional_parameter("topic", tmp, MAX_FILENAME); if(strlen(tmp)) { if(*loc == -1) cs_critical_error(ERR_PARAM_MISSING, "loc"); *topic = atoi(tmp); } else *topic = -1; /* just check if the "del_link" parameter is there */ cs_get_optional_parameter("del_link", tmp, 5); *del_link = strlen(tmp); /* shared_cgi_util.c */ /* provide printer-friendly version? */ cs_get_optional_parameter ("print", tmp, 5); /* shared_cs_util.c */ *printer_friendly = *tmp? (atoi(tmp)? 1 : 0) : 0; /* provide preformatted text (instead of proportional) ? */ cs_get_optional_parameter ("fixed", tmp, 5); /* shared_cs_util.c */ *fixed_font = *tmp? (atoi(tmp)? 1 : 0) : 0; } static void parse_gather_string (char *gather_string, int target_topic, long topic_offset) { char *ptr; global_gather_criteria.topic_offset = topic_offset; global_gather_criteria.target_topic = target_topic; *global_gather_criteria.username = '\0'; *global_gather_criteria.realname = '\0'; global_gather_criteria.time_read = 0; if (!strcmp (gather_string, GATHER_ALL)) global_gather_criteria.target = ALL; else if (!strcmp (gather_string, GATHER_UNREAD)) global_gather_criteria.target = UNREAD; else if (!strcmp (gather_string, GATHER_FLAGGED)) global_gather_criteria.target = FLAGGED; else if (!strcmp (gather_string, GATHER_LAST)) global_gather_criteria.target = LAST; else { ptr = strchr (gather_string, ':'); if (ptr) { *ptr = '\0'; strncpy (global_gather_criteria.username, gather_string, MAX_USERNAME); strncpy (global_gather_criteria.realname, ptr + 1, MAX_NAME); *ptr = ':'; global_gather_criteria.target = USERNAME; } else cs_critical_error (ERR_GATHER_PARSE_ERROR, ""); } } static void last_io_error () { cs_critical_error (ERR_LAST_FILE_IO_ERROR, ""); } static void free_attachment_list() { ATTACHMENT_NODE *ptr; while(global_attachment_head) { ptr = global_attachment_head; global_attachment_head = global_attachment_head -> next; free(ptr); } } static void read_last_criteria () { char last_path[MAX_PATH + 1]; FILE *fp; snprintf (last_path, MAX_PATH + 1, "%s%s", discussion_path, GATHER_LAST_FNAME); fp = fopen (last_path, "r"); if (!fp) last_io_error (); if (fread (&global_gather_criteria, sizeof (GATHER_STRUCT), 1, fp) != 1) last_io_error (); fclose (fp); } static void write_last_criteria () { char last_path[MAX_PATH + 1]; FILE *fp; if( (global_gather_criteria.target == UNREAD) && !global_gather_criteria.time_read ) global_gather_criteria.time_read = global_current_time; snprintf (last_path, MAX_PATH + 1, "%s%s", discussion_path, GATHER_LAST_FNAME); fp = fopen (last_path, "w"); if (!fp) last_io_error (); if (fwrite (&global_gather_criteria, sizeof (GATHER_STRUCT), 1, fp) != 1) last_io_error (); fclose (fp); } static void set_basic_values () { char timestring[MAX_TIMESTRING + 1]; char url[MAX_PATH +1]; cs_set_current_time(); /* put in the BACK button */ if (global_gather_criteria.target_topic != -1) /* then return to "assign_fac_expand" */ { snprintf (url, MAX_PATH + 1, "%s?crs=%s&id=%s&topic=%d&loc=%ld", "assign_fac_expand", global_course, global_key, global_gather_criteria.target_topic,global_gather_criteria.topic_offset); cs_set_value("back_url", url); } else { snprintf (url, MAX_PATH +1, "%s?crs=%s&id=%s&grp=%d", "news_topic", global_course, global_key, ASSIGNMENTS); cs_set_value("back_url", url); } cs_set_value("gather_from_assignment_list", global_gather_criteria.target_topic == -1 ? "1" : "0"); cs_set_value("gather_one_assignment", global_gather_criteria.topic_offset == -1? "0": "1"); switch(global_gather_criteria.target) { case UNREAD: cs_set_value("gather_target", "UNREAD"); break; case FLAGGED: cs_set_value("gather_target", "FLAGGED"); break; case ALL: cs_set_value("gather_target", "ALL"); break; case USERNAME: cs_set_value("gather_target", "USERNAME"); cs_set_value("gather_target_realname", global_gather_criteria.realname); cs_set_value("gather_target_username", global_gather_criteria.username); cs_set_value("gather_target_is_teacher", global_teacher_search? "1": "0"); break; default: break; } strftime (timestring, MAX_TIMESTRING, DOW_DATE_TIME_FORMAT, localtime (&global_current_time)); cs_set_value("global_time", timestring); } static void set_flag_reply_info_urls ( const char *name_prefix, MSG_TREE_NODE *msg_ptr) { #define MAX_TMP_NAME 50 static char from_string[MAX_PATH + 1]; /* how to get back to THIS page... passed ** to the NEWS_NEW_MEMO program */ char name[MAX_TMP_NAME]; char url[MAX_PATH *2+1]; snprintf (from_string, MAX_PATH + 1, "from=%s%ccrs%c%s%cid%c%s%cgather%c_last_%c%ld", "assign_gather", QMARK_SUB, EQUAL_SUB, global_course, AMP_SUB, EQUAL_SUB, global_key, AMP_SUB, EQUAL_SUB, POUND_SUB, msg_ptr->offset); /** set flag value and url */ snprintf(name, MAX_TMP_NAME, "%s.flagged", name_prefix); cs_set_int_value(name, msg_ptr->user_data.flagged? 1:0); snprintf(url, MAX_PATH * 2, "%s?crs=%s&id=%s&grp=%d&msg_id=%d&%s", "news_flag", global_course, global_key, ASSIGNMENTS, msg_ptr->data.info.msg_id, from_string); snprintf(name, MAX_TMP_NAME, "%s.flag_url", name_prefix); cs_set_value(name, url); /*** REPLY button only if msg was sent by a student **** button is disabled if this user doesn't have write access ***/ if(!strcmp(msg_ptr->data.info.sender, msg_ptr->data.info.receiver_username)) { if(global_can_write) { snprintf(name, MAX_TMP_NAME, "%s.reply_url", name_prefix); snprintf (url, MAX_PATH *2 +1,"%s?crs=%s&id=%s&grp=%d&topic=%d&loc=%ld&reply_to=%d&to=%s&%s", "news_new_memo", global_course, global_key, ASSIGNMENTS, msg_ptr->data.info.topic_id, msg_ptr->offset, msg_ptr->data.info.msg_id, msg_ptr->data.info.sender, from_string); cs_set_value(name, url); } } /**** Always provide an INFO button ***/ snprintf(name, MAX_TMP_NAME, "%s.info_url", name_prefix); snprintf (url, MAX_PATH +1, "%s?crs=%s&id=%s&loc=%ld&grp=%d", "news_info", global_course, global_key, msg_ptr->offset, ASSIGNMENTS); cs_set_value(name, url); #undef MAX_TMP_NAME } int gather_msg_should_display(MSG_TREE_NODE *msg_ptr, char *username) { /* always show the assignment */ if(msg_ptr->data.info.reply_to == -1) return 1; switch(global_gather_criteria.target) { case USERNAME: case ALL: return (!strcmp(msg_ptr->data.info.sender, username) || !strcmp(msg_ptr->data.info.receiver_username, username) ); break; case FLAGGED: return msg_ptr->user_data.flagged; break; case UNREAD: return (global_gather_criteria.time_read == msg_ptr->user_data.time_read); break; default: return 0; /* this should never happen */ break; } } static void process_msg_node (MSG_TREE_NODE *msg_ptr, int *website_attached) { FILE *fp; int fd; char infopath[MAX_PATH + 1]; int i; NEWS_INFO infofile_info; ATTACHMENT_NODE *aptr = 0; if(!msg_ptr->user_data.time_read && (global_conf.access != ACCESS_CAPTURE)) /* if msg is unread and not in capture mode */ { mark_msg_as_read(&(msg_ptr->data),&global_conf,&global_user); /*shared_news_util.c */ } /*** if there are attachments, read the info file to **** build the attachment list ***/ *website_attached = 0; /* assume this to start */ if(msg_ptr->data.info.attachments) { snprintf(infopath,MAX_PATH + 1,"%s%s/%s/%s/%s",global_conf.course_path,PEOPLE_DIR, msg_ptr->data.info.sender,discussion_fname,msg_ptr->data.info_fname); fp = fopen(infopath,"r"); if(!fp) cs_critical_error (ERR_FOPEN_READ_FAILED, infopath); fd = fileno(fp); get_shared_lock(fd, 1); /* shared_lock.c */ if(fread( &infofile_info, sizeof(NEWS_INFO), 1, fp) != 1) cs_critical_error (ERR_FREAD_FAILED, infopath); /* process attachments, build linked list */ *website_attached = (infofile_info.attachments == -1); if (*website_attached) infofile_info.attachments = 1; /* since we have to pick up the dirname */ for (i = 1; i <= infofile_info.attachments; i++) { if (!global_attachment_head) { global_attachment_head = (ATTACHMENT_NODE *) malloc (sizeof (ATTACHMENT_NODE)); if (!global_attachment_head) cs_critical_error (ERR_MALLOC_FAILED, infopath); aptr = global_attachment_head; } else { aptr->next = (ATTACHMENT_NODE *) malloc (sizeof (ATTACHMENT_NODE)); if (!aptr->next) cs_critical_error (ERR_MALLOC_FAILED, infopath); aptr = aptr->next; } if (fread(&aptr->data, sizeof (ATTACHMENT), 1, fp) != 1) cs_critical_error (ERR_FREAD_FAILED, infopath); aptr->next = 0; } /* for .. */ release_lock(fd); /* shared_lock.c */ fclose (fp); } /* if attachments */ } static void print_message(MSG_TREE_NODE *msg_ptr, char *username, char *realname) { #define MAX_TMP_NAME 50 static int last_topic = -1; static char last_username[MAX_USERNAME + 1] = ""; static int topic_number = -1; static int msg_number = -1; static int student_number = -1; char name_prefix[MAX_TMP_NAME + 1]; char name[MAX_TMP_NAME + 1]; int website_attached; char msg_path[MAX_PATH + 1]; if(gather_msg_should_display(msg_ptr, username)) { /* if the topic has changed since the last time this was called, ** adjust the counts */ if(msg_ptr->data.info.topic_id != last_topic) { last_topic = msg_ptr->data.info.topic_id; topic_number++; msg_number = -1; student_number = -1; *last_username = '\0'; } if((msg_ptr->data.info.reply_to != -1) && strcmp(username, last_username)) { strncpy(last_username, username, MAX_USERNAME + 1); student_number++; msg_number = -1; } if(msg_ptr->data.info.reply_to == -1) /* if this is a topic */ snprintf(name_prefix,MAX_TMP_NAME + 1, "topic.%d", topic_number); else snprintf(name_prefix,MAX_TMP_NAME + 1, "topic.%d.student.%d.msg.%d", topic_number, student_number, ++msg_number); set_flag_reply_info_urls (name_prefix, msg_ptr); /* shared_news_util.c */ set_assign_read_heading_data (msg_ptr, name_prefix, global_topic_ptr, global_course, global_key, &global_conf, global_topic_teacher_hidden, global_can_write, global_topic_read_only); /* the above call does not know about the real name of the person we are working with, so ** we'll set it 'manually' here **/ snprintf(name, MAX_TMP_NAME, "%s.receiver_realname", name_prefix); cs_set_value(name, realname); snprintf(name, MAX_TMP_NAME, "%s.indent", name_prefix); cs_set_int_value(name, global_gather_criteria.target != UNREAD? global_indent: 0); process_msg_node (msg_ptr, &website_attached); /* shared_news_util.c */ set_news_attachment_data (msg_ptr, global_topic_ptr->data.info.subject, global_course, global_key, ASSIGNMENTS, website_attached, "assign_read", global_user.team, /* but it doesn't really matter */ 0, /* is_fac_teamteach */ last_username, /* THIS MAY BE WRONG! - checked only quickly */ &global_conf, 0, /* attachments locked, should be 0 because there's a teacher running this */ global_user.username, global_attachment_head, name_prefix); /* name prefix is blank */ free_attachment_list(); snprintf (msg_path, MAX_PATH + 1, "%s%s/%s/%s/%s", global_conf.course_path, PEOPLE_DIR, msg_ptr->data.info.sender, discussion_fname, msg_ptr->data.info.msg_fname); strcat(name_prefix, "."); /* stupid, because next expects a dot after the prefix */ set_msg_body_data(msg_path, name_prefix, ""); /* shared_msg_line.c */ } #undef MAX_TMP_NAME } static void gather_subtree (MSG_TREE_NODE *root, int indent) { if(!root) return; if(indent) global_indent++; print_message(root, global_username, global_realname); if(root->reply) gather_subtree(root->reply, 1); if(root->next) gather_subtree(root->next, 0); if(indent) global_indent--; } P_NODE *build_user_list() { P_NODE *p_head, *p_ptr; /* if we are looking for msgs by a particular person, ** just put that one person in the list */ if(global_gather_criteria.target == USERNAME) { p_head = (P_NODE *) malloc (sizeof (P_NODE)); if (!p_head) cs_critical_error (ERR_MALLOC_FAILED, ""); strncpy(p_head->data.username,global_gather_criteria.username, MAX_USERNAME + 1); strncpy(p_head->data.realname,global_gather_criteria.realname, MAX_NAME + 1); global_person_head = build_option_person_list(&global_conf); /* shared_person_list.c */ /* look for the username in the password file */ for(p_ptr=global_person_head; p_ptr && strcmp(p_ptr->data.username, global_gather_criteria.username); p_ptr=p_ptr->next); if(!p_ptr) /* for some reason, user wasn't in the password file - give bogus values */ { *(p_head->data.id) = '\0'; p_head->data.team = 'Z'; p_head->data.group = STUDENT; } else { strncpy(p_head->data.id, p_ptr->data.id, MAX_ID + 1); p_head->data.team = p_ptr->data.team; p_head->data.group = p_ptr->data.group; } p_head->next = (P_NODE *) 0; } else /* for all other targets, build a complete list of users */ p_head = build_option_person_list(&global_conf); /* shared_person_list.c */ return (p_head); /* for complete correctness, we should add to the list created by build_option_person_list ** those students who posted something to ASSIGNMENTS, but have since been removed from the ** course. That is, gathering 'all' will NOT show msgs by those deleted students. ** maybe an enhancement? */ } static void gather_news_msg ( char *gather_string, int target_topic, long topic_offset) { MSG_TREE_NODE *msg_ptr, *msg_ptr2; MSG_TREE_NODE *topic, *msg; int msg_count; P_NODE *person_head, *p_ptr; parse_gather_string (gather_string, target_topic, topic_offset); if (global_gather_criteria.target == LAST) read_last_criteria(); if(global_gather_criteria.topic_offset == -1) /* if we are looking at ALL assignments (instead of just one) */ build_msg_tree(&global_user, ASSIGNMENTS, 0, 1, &global_current_time); /* shared_news_util.c */ else { build_one_topic_tree(&global_user, ASSIGNMENTS, global_gather_criteria.topic_offset, /* shared_news_util.c */ &msg, &topic, &msg_count, 0, 1, &global_current_time); /* the above call must be completely successful */ if( (!tree_head || !msg) || (msg != topic) ) cs_critical_error(ERR_CANT_FIND_THIS_OFFSET, ""); } person_head = build_user_list(); /* build a list of people to search for */ /*are we looking for a teacher's posts? */ global_teacher_search = ((global_gather_criteria.target == USERNAME) && (person_head && person_head->data.group == FACULTY) ); set_basic_values (); for(msg_ptr=tree_head; msg_ptr; msg_ptr=msg_ptr->next) /* for each assignment */ { global_indent = 0; global_topic_ptr = msg_ptr; /* copy the topic subject to the global variable */ strncpy(global_topic_subject,msg_ptr->data.info.subject, MAX_SUBJECT + 1); global_topic_teacher_hidden = is_teacher_hidden (msg_ptr->data.status, msg_ptr->data.release_time, /* shared_news_util.c */ msg_ptr->data.unrelease_time, global_current_time ); global_topic_read_only = BASIC_STATUS(msg_ptr->data.status) == READ_ONLY? 1 : 0; if(global_teacher_search) /* special case - we are looking for msgs posted by a teacher */ { /* copy names to global variables */ strncpy(global_username, person_head->data.username, MAX_USERNAME + 1); strncpy(global_realname, person_head->data.realname, MAX_NAME + 1); if(!strcmp(msg_ptr->data.info.sender,global_username)) print_message(msg_ptr,"",""); /* print the assignment if it was posted by the teacher we ** are targeting */ gather_subtree(msg_ptr->reply, 1); } else /* not a global_teacher_search */ { /* Show the assignment when ** You're searching for anything but UNREAD or FLAGGED messages OR ** You're searching for UNREAD msgs, and the assignment is unread OR ** You're searching for FLAGGED msgs, and the assignment is flagged. */ if( ((global_gather_criteria.target != UNREAD) && (global_gather_criteria.target != FLAGGED)) || ( ((global_gather_criteria.target == UNREAD) && (!msg_ptr->user_data.time_read)) || ((global_gather_criteria.target == FLAGGED) && (msg_ptr->user_data.flagged)) ) ) { print_message(msg_ptr,"",""); } for(p_ptr = person_head; p_ptr; p_ptr = p_ptr->next) /* for each person on the list */ { /* copy names to global variables */ strncpy(global_username, p_ptr->data.username, MAX_USERNAME + 1); strncpy(global_realname, p_ptr->data.realname, MAX_NAME + 1); for(msg_ptr2 = msg_ptr->reply; msg_ptr2; msg_ptr2 = msg_ptr2->next) { if(!strcmp(msg_ptr2->data.info.receiver_username, p_ptr->data.username)) { print_message(msg_ptr2, p_ptr->data.username, p_ptr->data.realname); gather_subtree(msg_ptr2->reply, 1); } } } } /* else not a global_teacher_search */ } /* for each assignment */ free_option_person_list(person_head); /* shared_person_list.c */ free_option_person_list(global_person_head); /* shared_person_list.c */ free_msg_tree (tree_head); /* shared_news_util.c */ write_last_criteria(); } int main (void) { int del_link; /* from del_link=?command line do we need to rm a symbolic link ** to an attached web page? */ int target_topic; /* from optional topic=? command line */ long topic_offset; /* from optional loc=? command line */ char gather_string[MAX_PATH + 1]; int printer_friendly; /* from print=? command line provide "printer friendly" version? */ int fixed_font; /* from fixed=? command line print body as fixed font? */ cs_cgi_init(); read_parameters (global_course,global_key, gather_string, &del_link, &target_topic, &topic_offset, &printer_friendly, &fixed_font); read_configuration_file (global_course, &global_conf); /* shared_util.c */ validate_key (global_key, &global_user, &global_conf); /* shared_util.c */ if(global_user.group != FACULTY) /* only teachers can run this program */ cs_critical_error(ERR_REQUEST_DENIED, ""); cs_set_value("is_faculty","1"); if(global_conf.access == ACCESS_CAPTURE) cs_set_value("capture_mode", "1"); /* shared_cs_util.h */ cs_set_int_value("printer_friendly", printer_friendly); cs_set_int_value("fixed_font", fixed_font); global_can_write = has_write_permission(&global_user, &global_conf); /* shared_access.c */ if(global_can_write) cs_set_value("can_write", "1"); cs_set_course_info(&global_conf); /* shared_cs_util.c */ cs_set_value("crs", global_course); cs_set_int_value("grp", ASSIGNMENTS); cs_set_value("key", global_key); cs_set_value("username",global_user.username); set_discussion_names (global_conf.course_path, global_user.username, ASSIGNMENTS); /* shared_news_util.c */ gather_news_msg ( gather_string, target_topic, topic_offset); if (del_link) remove_stale_symlinks (global_user.username, global_course, global_conf.access); /* shared_news_util.c */ /* NOTE: this program uses the same lang file as news_read.c **/ cs_load_lang("news_read"); cs_cgi_display("assign_gather", 0); cs_cgi_destroy(); return 0; }