#include #include #include #include /* for tolower() */ #include "global.h" #include "custom.h" /* for date FORMAT strings */ #include "survey.h" #include "manhat-lib/shared_util.h" #include "manhat-lib/shared_cs_util.h" #include "manhat-lib/shared_news_util.h" #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 NOTEAM '!' #define GATHER_ALL "_all_" #define GATHER_FLAGGED "_flagged_" #define GATHER_UNREAD "_unread_" #define GATHER_LAST "_last_" #define GATHER_TOPIC "_topic_" typedef enum gather_type { UNREAD, FLAGGED, ALL_MSG, USERNAME, TOPIC, LAST } GATHER_TYPE; typedef struct gather_struct { int topic_id; 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; int global_indent = -1; int global_msg_count = 0; int global_topic_count = 0; int global_total_msgs = 0; #define NO_INDENT 0 #define INDENT 1 /* these are declared globally to reduce overhead ** for a recursive function in this program **/ GATHER_STRUCT gather_criteria; time_t current_time; SESSION user; CONFIG_STRUCT conf; /* the configuration read from config file */ char course[MAX_PATH + 1]; /* from crs=? command line */ char key[MAX_KEY + 1]; /* from id=? command line */ int grp; /* from grp=? command line */ int is_fac_teamteach; char topic_subject[MAX_SUBJECT + 1]; int topic_teacher_hidden; /* did the teacher hide the topic */ int topic_read_only; /* is the topic marked read only */ int global_can_write; /* can this user add msgs to this course? */ ATTACHMENT_NODE *attachment_head = (ATTACHMENT_NODE *) 0; static void read_parameters (char *course, char *key, char *gather, int *grp, int *del_link, char *topic_prog, char *team, int *printer_friendly, int *fixed_font) { char tmp[MAX_FILENAME + 1]; /* scratch variable */ cs_get_required_parameter ("crs", course, MAX_FILENAME); cs_get_required_parameter ("id", key, MAX_KEY); cs_get_required_parameter ("grp", tmp, MAX_FILENAME); *grp = atoi (tmp); cs_get_optional_parameter ("gather", gather, MAX_PATH); if(!strlen(gather)) strncpy (gather, GATHER_LAST, MAX_PATH + 1); snprintf (topic_prog, MAX_PATH + 1, "%s?crs=%s&id=%s&grp=%d", "news_topic", course, key, *grp); cs_get_optional_parameter ("team", tmp, 5); if(!strlen(tmp) ) *team = NOTEAM; else *team = *tmp; /* just check if the "del_link" parameter is there */ cs_get_optional_parameter ("del_link", tmp, 5); *del_link = strlen(tmp); /* 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; } /* is this the teacher working with the team/teacher module? */ static void check_is_fac_team_teach (int grp, int *is_fac_teamteach, char *team, char *topic_prog, SESSION *user) { char teamstring[20]; if ((user->group == FACULTY) && (grp == TEAM_TEACH)) { /* then the 'team' param must have been passed to this program */ if(*team == NOTEAM) cs_critical_error(ERR_PARAM_MISSING, "team"); *is_fac_teamteach = 1; /* append the team parameter to the ** topic_prog string, which will be eventually ** used to set a back button url */ snprintf (teamstring, 20, "&team=%c", *team); strcat (topic_prog, teamstring); } else { *is_fac_teamteach = 0; *team = NOTEAM; /* NOTEAM is #defined on top of this file. */ } } static void parse_gather_string (char *gather, GATHER_STRUCT * gather_target) { char *ptr; gather_target->topic_id = -1; *gather_target->username = '\0'; *gather_target->realname = '\0'; gather_target->time_read = 0; if (!strcmp (gather, GATHER_ALL)) gather_target->target = ALL_MSG; else if (!strcmp (gather, GATHER_FLAGGED)) gather_target->target = FLAGGED; else if (!strcmp (gather, GATHER_UNREAD)) gather_target->target = UNREAD; else if (!strcmp (gather, GATHER_LAST)) gather_target->target = LAST; else { ptr = strchr (gather, ':'); if (ptr) { *ptr = '\0'; strncpy (gather_target->username, gather, MAX_USERNAME); strncpy (gather_target->realname, ptr + 1, MAX_NAME); *ptr = ':'; gather_target->target = USERNAME; } else { ptr = strstr (gather, GATHER_TOPIC); if (ptr == gather) { ptr += strlen (GATHER_TOPIC); gather_target->topic_id = atoi (ptr); gather_target->target = TOPIC; } else cs_critical_error (ERR_GATHER_PARSE_ERROR, gather); } } } static void memory_error () { cs_critical_error (ERR_MALLOC_FAILED, ""); }; static void free_attachment_list() { ATTACHMENT_NODE *ptr; while(attachment_head) { ptr = attachment_head; attachment_head = attachment_head -> next; free(ptr); } } static void build_attachment_list (MSG_TREE_NODE * msg) { FILE *fp; int fd; char infopath[MAX_PATH + 1]; int i; ATTACHMENT_NODE *aptr = 0; NEWS_INFO info; int n_attachments; free_attachment_list(); if(!msg->data.info.attachments) return; n_attachments = abs(msg->data.info.attachments); /* since a -1 is used to flag an attached website */ snprintf (infopath, MAX_PATH + 1, "%s%s/%s/%s/%s", conf.course_path, PEOPLE_DIR, msg->data.info.sender, discussion_fname, msg->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 (&info, sizeof (NEWS_INFO), 1, fp) != 1) cs_critical_error (ERR_FREAD_FAILED, infopath); /* process attachments, build linked list */ for (i = 1; i <= n_attachments; i++) { if (!attachment_head) { attachment_head = (ATTACHMENT_NODE *) malloc (sizeof (ATTACHMENT_NODE)); if (!attachment_head) memory_error (); aptr = attachment_head; } else { aptr->next = (ATTACHMENT_NODE *) malloc (sizeof (ATTACHMENT_NODE)); if (!aptr->next) memory_error (); 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); } static void read_last_criteria (GATHER_STRUCT * gather_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) cs_critical_error(ERR_FOPEN_READ_FAILED, last_path); if (fread (gather_criteria, sizeof (GATHER_STRUCT), 1, fp) != 1) cs_critical_error(ERR_FREAD_FAILED, last_path); fclose (fp); } static void write_last_criteria (GATHER_STRUCT * gather_criteria) { char last_path[MAX_PATH + 1]; FILE *fp; if( (gather_criteria->target == UNREAD) && !gather_criteria->time_read ) gather_criteria->time_read = current_time; snprintf (last_path, MAX_PATH + 1, "%s%s", discussion_path, GATHER_LAST_FNAME); fp = fopen (last_path, "w"); if (!fp) cs_critical_error(ERR_FOPEN_WRITE_FAILED, last_path); if (fwrite (gather_criteria, sizeof (GATHER_STRUCT), 1, fp) != 1) cs_critical_error(ERR_FWRITE_FAILED, last_path); fclose (fp); } static void set_gather_target (GATHER_STRUCT * gather_criteria) { switch(gather_criteria -> target) { case UNREAD: cs_set_value("gather_target", "UNREAD"); break; case FLAGGED: cs_set_value("gather_target", "FLAGGED"); break; case ALL_MSG: cs_set_value("gather_target", "ALL"); break; case USERNAME: cs_set_value("gather_target", "USERNAME"); cs_set_value("gather_target_realname", gather_criteria->realname); cs_set_value("gather_target_username", gather_criteria->username); break; case TOPIC: cs_set_value("gather_target", "TOPIC"); /** additional values will be set later via set_topic_target_details() */ break; case LAST: /* LAST should have been replaced via call to read_last_criteria() */ default: cs_set_value("gather_target", ""); break; /* should never happen? */ } } static void set_topic_target_details(MSG_TREE_NODE *msg_ptr) { char *escaped_string = 0; cs_html_escape_string(msg_ptr->data.info.subject, &escaped_string); /* shared_cs_util.c */ if(escaped_string) { cs_set_value("target_escaped_topic_subject", escaped_string); free(escaped_string); } cs_set_value( "target_topic_subject", msg_ptr->data.info.subject); cs_set_value( "target_topic_sender_realname", msg_ptr->data.info.sender_realname); } static void get_supporting_strings (char *fromstring, char *teamstring, long offset) { char escaped_teamstring[10]; /* AMP_SUB, etc. are #defined in shared_translate_url.h */ if (is_fac_teamteach) { snprintf (teamstring, 20, "&team=%c", user.team); snprintf (escaped_teamstring, 10, "%cteam%c%c", AMP_SUB, EQUAL_SUB, user.team); } else { *teamstring = '\0'; *escaped_teamstring = '\0'; } snprintf (fromstring, MAX_PATH + 1, "from=%s%ccrs%c%s%cid%c%s%cgrp%c%d%cgather%c_last_%s%c%ld", "news_gather", QMARK_SUB, EQUAL_SUB, course, AMP_SUB, EQUAL_SUB, key, AMP_SUB, EQUAL_SUB, grp, AMP_SUB, EQUAL_SUB, escaped_teamstring, POUND_SUB, offset); } static void set_indent(const char *name) { char msg_name[MAX_PATH + 1]; snprintf(msg_name, MAX_PATH, "%sindent",name); if( (gather_criteria.target == TOPIC) || (gather_criteria.target == ALL_MSG) ) cs_set_int_value(msg_name, global_indent); else cs_set_int_value(msg_name, 0); } static void set_flag_reply_info_urls ( MSG_TREE_NODE *msg_ptr, char *name) { char from_string[MAX_PATH + 1]; /* how to get back to THIS page... passed ** to the NEWS_NEW_MEMO program */ char teamstring[20]; /* eg. "team=A" */ char msg_name[MAX_PATH]; char url[MAX_PATH*2]; get_supporting_strings (from_string, teamstring, msg_ptr->offset); /** set flag value and url */ snprintf(msg_name, MAX_PATH, "%sflagged", name); cs_set_int_value(msg_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", course, key, grp, msg_ptr->data.info.msg_id, from_string); snprintf(msg_name, MAX_PATH, "%sflag_url", name); cs_set_value(msg_name, url); if (group_uses_topic (grp)) /* shared_news_util.c */ { /*** REPLY button */ if(global_can_write) { if( (user.group == FACULTY) || !topic_read_only) { snprintf(msg_name, MAX_PATH, "%sreply_url", name); snprintf (url, MAX_PATH *2, "%s?crs=%s&id=%s&grp=%d&topic=%d&loc=%ld%s&reply_to=%d", "news_new_memo", course, key, grp, msg_ptr->data.info.topic_id, msg_ptr->offset, teamstring,msg_ptr->data.info.msg_id); if((grp == ASSIGNMENTS) && (user.group == STUDENT) ) { strcat(url, "&to="); strcat(url, user.username); } strcat(url, "&"); strcat(url, from_string); cs_set_value(msg_name, url); } } } /* if group uses topic */ /**** INFO ***/ if(grp == SURVEYS) snprintf (url, MAX_PATH *2, "%s?crs=%s&id=%s&loc=%ld", "survey_inf", course, key, msg_ptr->offset); else snprintf (url, MAX_PATH *2, "%s?crs=%s&id=%s&loc=%ld&grp=%d", "news_info", course, key, msg_ptr->offset, grp); snprintf(msg_name, MAX_PATH, "%sinfo_url", name); cs_set_value(msg_name, url); } int gather_msg_should_display(MSG_TREE_NODE *msg_ptr) { switch(gather_criteria.target) { case ALL_MSG: return 1; break; case TOPIC: return 1; /* since we've already ensured we are looking at the correct topic */ break; case FLAGGED: return msg_ptr->user_data.flagged ? 1 : 0; break; case UNREAD: return (gather_criteria.time_read == msg_ptr->user_data.time_read); break; case USERNAME : return (!strcmp(gather_criteria.username, msg_ptr->data.info.sender) ); break; default: return 0; /* this should never happen */ break; } } static void set_message_data(MSG_TREE_NODE *msg_ptr) { #define MAX_HDF_NAME 40 char name[MAX_HDF_NAME]; char msg_path[MAX_PATH + 1]; char inet_url[MAX_URL + 1]; if(gather_msg_should_display(msg_ptr)) { /* Do not mark message as read if: ** - the attachments are protected or ** - this is the surveys module and the reader is not a teacher. */ if(!msg_ptr->user_data.time_read) { if(grp == SURVEYS) { if(user.group != FACULTY) mark_msg_as_read_at_time(&(msg_ptr->data), &conf, &user,current_time); /* shared_news_util.c */ } else { if(!IS_PROTECTED(msg_ptr->data.status)) mark_msg_as_read_at_time(&(msg_ptr->data), &conf, &user,current_time); /* shared_news_util.c */ } } /* if msg not read */ if(msg_ptr->data.info.reply_to == -1) /* i.e. if this is a topic */ { global_topic_count++; global_msg_count = 0; } snprintf(name, MAX_HDF_NAME + 1, "topic.%d.msg.%d.", global_topic_count-1, global_msg_count); global_msg_count++; set_indent (name); set_flag_reply_info_urls (msg_ptr, name); /* shared_news_util.c */ set_news_msg_header_data (msg_ptr, name, topic_teacher_hidden, topic_read_only, topic_subject); build_attachment_list (msg_ptr); /* shared_news_util.c */ set_news_attachment_data (msg_ptr, topic_subject, course, key, grp, msg_ptr->data.info.attachments == -1 ? 1: 0, "news_gather", user.team, is_fac_teamteach, "", /* no assign_read_student value */ &conf, /* next is the is_protected param */ (msg_ptr->data.info.attachments && IS_PROTECTED(msg_ptr->data.status) && !msg_ptr->user_data.time_read)? 1: 0, user.username, attachment_head, name); /* handle body of message */ snprintf (msg_path, MAX_PATH + 1, "%s%s/%s/%s/%s", conf.course_path, PEOPLE_DIR, msg_ptr->data.info.sender, discussion_fname, msg_ptr->data.info.msg_fname); if(grp == INTERNET_RESOURCES) snprintf(inet_url, MAX_URL + 1, "%s?crs=%s&id=%s&loc=%ld&gather=1&url=", "link_visit", course, key, msg_ptr->offset); else *inet_url = '\0'; set_msg_body_data (msg_path, name, inet_url); /* shared_msg_line.c */ global_total_msgs++; } #undef MAX_HDF_NAME } static void gather_subtree (MSG_TREE_NODE *root, int indent) { if(!root) return; if(indent) global_indent++; set_message_data(root); if(root->reply) gather_subtree(root->reply, INDENT); if(root->next && (root->next->data.info.reply_to != -1)) /* don't do 'next' branches if they lead to a topic */ gather_subtree(root->next, NO_INDENT); if(indent) global_indent--; } static void handle_one_topic(MSG_TREE_NODE *msg_ptr) { if(msg_ptr) /* this should always be true */ { /* topic_subject is global */ strncpy(topic_subject,msg_ptr->data.info.subject, MAX_SUBJECT + 1); /* topic_teacher_hidden and current_time are global * is_teacher_hidden() is in shared_news_util.c */ topic_teacher_hidden = is_teacher_hidden (msg_ptr->data.status, msg_ptr->data.release_time, msg_ptr->data.unrelease_time, current_time ); /* topic_read_only is global at top of this file; ** BASIC_STATUS is macro #defined in global.h */ topic_read_only = BASIC_STATUS(msg_ptr->data.status) == READ_ONLY? 1 : 0; gather_subtree(msg_ptr, INDENT); } } static void set_gather_news_values (char *course, char *key, char *gather, int grp, char team, int is_fac_teamteach, char *topic_prog, CONFIG_STRUCT *conf, SESSION *user) { MSG_TREE_NODE *msg_ptr; /* note that gather_criteria is declared globally */ parse_gather_string (gather, &gather_criteria); if (gather_criteria.target == LAST) read_last_criteria(&gather_criteria); set_gather_target(&gather_criteria); /* current_time is declared globally */ build_msg_tree(user, grp, 0, 1, ¤t_time); /* shared_news_util.c */ if(gather_criteria.target == TOPIC) /* then search just that topic */ { /* find the topic */ for(msg_ptr=tree_head; msg_ptr && msg_ptr->data.info.topic_id != gather_criteria.topic_id; msg_ptr=msg_ptr->next) { /* do nothing */ } set_topic_target_details(msg_ptr); handle_one_topic(msg_ptr); } else { for(msg_ptr=tree_head; msg_ptr; msg_ptr=msg_ptr->next) handle_one_topic(msg_ptr); } cs_set_int_value("msg_count", global_total_msgs); free_msg_tree (tree_head); /* shared_news_util.c */ write_last_criteria(&gather_criteria); } int main (void) { char gather[MAX_PATH + 1]; /* from gather=? command line */ char team; /* from team=? command line: used only for instructors in the team/teach module */ int del_link; /* from del_link=? command line do we need to rm a symbolic link ** to an attached web page? */ int printer_friendly; /* from print=? command line provide "printer friendly" version? */ int fixed_font; /* from fixed=? command line print body as fixed font? */ char topic_prog[MAX_PATH + 1]; /* arguments to NEWS_TOPIC program */ char teamstring[2]; /* note that course, key, grp, is_fac_teamteach, user, and conf are declared globally in this program */ cs_cgi_init(); read_parameters (course, key, gather, &grp, &del_link, topic_prog, &team, &printer_friendly, &fixed_font); read_configuration_file (course, &conf); /* shared_util.c */ validate_key (key, &user, &conf); /* shared_util.c */ if(user.group == FACULTY) cs_set_value("is_faculty","1"); /* shared_cs_util.h */ if(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(&user, &conf); /* shared_access.c */ if(global_can_write) cs_set_value("can_write", "1"); /* shared_cs_util.h */ cs_set_course_info(&conf); /* shared_cs_util.c */ cs_set_current_time(); /* shared_cs_util.c */ cs_set_value("crs", course); /* shared_cs_util.h */ cs_set_int_value("grp", grp); /* shared_cs_util.h */ if(group_uses_topic(grp)) /* shared_news_util.c */ cs_set_value("group_uses_topic", "1"); cs_set_value("key", key); /* shared_cs_util.h */ cs_set_value("username",user.username); /* shared_cs_util.h */ set_discussion_names (conf.course_path, user.username, grp); /* shared_news_util.c */ check_is_fac_team_teach (grp, &is_fac_teamteach, &team, topic_prog, &user); cs_set_value("back_url", topic_prog); if(is_fac_teamteach) { user.team = team; /* set teacher to this team for the run of this program */ cs_set_value("is_fac_teamteach", "1"); } if(team != NOTEAM) { *teamstring = team; *(teamstring + 1) = '\0'; cs_set_value("team", teamstring); /* shared_cs_util.h */ } set_gather_news_values (course, key, gather, grp, team, is_fac_teamteach, topic_prog, &conf, &user); if (del_link) remove_stale_symlinks (user.username, course, 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("news_gather", 0); cs_cgi_destroy(); return 0; }