Java tutorial
/********************************************************************************** * $URL$ * $Id$ *********************************************************************************** * * Copyright (c) 2004, 2005, 2006, 2007, 2008, 2009 The Sakai Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * **********************************************************************************/ package org.sakaiproject.tool.assessment.ui.bean.delivery; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.ResourceBundle; import java.util.Set; import javax.faces.application.FacesMessage; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.component.cover.ServerConfigurationService; import org.sakaiproject.event.cover.EventTrackingService; import org.sakaiproject.event.cover.NotificationService; import org.sakaiproject.exception.IdUnusedException; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.api.SitePage; import org.sakaiproject.site.api.ToolConfiguration; import org.sakaiproject.site.cover.SiteService; import org.sakaiproject.tool.api.Placement; import org.sakaiproject.tool.api.Session; import org.sakaiproject.tool.assessment.api.SamigoApiFactory; import org.sakaiproject.tool.assessment.data.dao.assessment.EventLogData; import org.sakaiproject.tool.assessment.data.dao.assessment.PublishedAssessmentData; import org.sakaiproject.tool.assessment.data.dao.assessment.PublishedItemData; import org.sakaiproject.tool.assessment.data.dao.assessment.PublishedItemText; import org.sakaiproject.tool.assessment.data.dao.assessment.PublishedSectionData; import org.sakaiproject.tool.assessment.data.dao.assessment.PublishedSecuredIPAddress; import org.sakaiproject.tool.assessment.data.dao.grading.AssessmentGradingData; import org.sakaiproject.tool.assessment.data.dao.grading.ItemGradingData; import org.sakaiproject.tool.assessment.data.dao.grading.MediaData; import org.sakaiproject.tool.assessment.data.ifc.assessment.AssessmentAccessControlIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.AssessmentBaseIfc; import org.sakaiproject.tool.assessment.data.ifc.shared.TypeIfc; import org.sakaiproject.tool.assessment.facade.AgentFacade; import org.sakaiproject.tool.assessment.facade.EventLogFacade; import org.sakaiproject.tool.assessment.facade.PublishedAssessmentFacade; import org.sakaiproject.tool.assessment.services.FinFormatException; import org.sakaiproject.tool.assessment.services.GradingService; import org.sakaiproject.tool.assessment.services.SaLengthException; import org.sakaiproject.tool.assessment.services.assessment.EventLogService; import org.sakaiproject.tool.assessment.services.assessment.PublishedAssessmentService; import org.sakaiproject.tool.assessment.shared.api.assessment.SecureDeliveryServiceAPI; import org.sakaiproject.tool.assessment.shared.api.assessment.SecureDeliveryServiceAPI.Phase; import org.sakaiproject.tool.assessment.shared.api.assessment.SecureDeliveryServiceAPI.PhaseStatus; import org.sakaiproject.tool.assessment.ui.bean.shared.PersonBean; import org.sakaiproject.tool.assessment.ui.bean.util.Validator; import org.sakaiproject.tool.assessment.ui.listener.delivery.BeginDeliveryActionListener; import org.sakaiproject.tool.assessment.ui.listener.delivery.DeliveryActionListener; import org.sakaiproject.tool.assessment.ui.listener.delivery.LinearAccessDeliveryActionListener; import org.sakaiproject.tool.assessment.ui.listener.delivery.SubmitToGradingActionListener; import org.sakaiproject.tool.assessment.ui.listener.select.SelectActionListener; import org.sakaiproject.tool.assessment.ui.listener.util.ContextUtil; import org.sakaiproject.tool.assessment.ui.listener.util.TimeUtil; import org.sakaiproject.tool.assessment.ui.model.delivery.TimedAssessmentGradingModel; import org.sakaiproject.tool.assessment.ui.queue.delivery.TimedAssessmentQueue; import org.sakaiproject.tool.assessment.ui.web.session.SessionUtil; import org.sakaiproject.tool.assessment.util.ExtendedTimeService; import org.sakaiproject.tool.assessment.util.MimeTypesLocator; import org.sakaiproject.tool.cover.SessionManager; import org.sakaiproject.tool.cover.ToolManager; import org.sakaiproject.util.ResourceLoader; import uk.org.ponder.rsf.state.support.TMLFixer; import org.apache.commons.lang.StringUtils; /** * * @author casong * @author esmiley@stanford.edu added agentState * $Id$ * * Used to be org.navigoproject.ui.web.asi.delivery.XmlDeliveryForm.java */ public class DeliveryBean implements Serializable { private static Log log = LogFactory.getLog(DeliveryBean.class); //SAM-2517 private ServerConfigurationService serverConfigurationService; private static final String MATHJAX_ENABLED = "mathJaxEnabled"; private static final String MATHJAX_SRC_PATH_SAKAI_PROP = "portal.mathjax.src.path"; private static final String MATHJAX_SRC_PATH = ServerConfigurationService.getString(MATHJAX_SRC_PATH_SAKAI_PROP, ""); private String assessmentId; private String assessmentTitle; private Boolean honorPledge = Boolean.FALSE; private ArrayList markedForReview; private ArrayList blankItems; private ArrayList markedForReviewIdents; private ArrayList blankItemIdents; private boolean reviewMarked; private boolean reviewAll; private boolean reviewBlank; private boolean displayMardForReview; private int itemIndex; private int size; private String action; private Date beginTime; private String endTime; private String currentTime; private String multipleAttempts; private String timeOutSubmission; private String submissionTicket; private String timeElapse; private String username; private int sectionIndex; private boolean previous; private String duration; private String url; private String confirmation; private String outcome; //Settings private String questionLayout; private String navigation; private String numbering; private String feedback; private String noFeedback; private String statistics; private String creatorName; private FeedbackComponent feedbackComponent; private String feedbackComponentOption; private boolean feedbackOnDate; private String errorMessage; private SettingsDeliveryBean settings; private java.util.Date dueDate; private java.util.Date adjustedTimedAssesmentDueDate; private java.util.Date retractDate; private boolean statsAvailable; private boolean submitted; private boolean graded; private String graderComment; private String rawScore; private String grade; private java.util.Date submissionDate; private java.util.Date submissionTime; private String image; private boolean hasImage; private String instructorMessage; private String courseName; private String timeLimit; private int timeLimit_hour; private int timeLimit_minute; private String timeLimitString; private ContentsDeliveryBean tableOfContents; private String submissionId; private String submissionMessage; private String instructorName; private ContentsDeliveryBean pageContents; private int submissionsRemaining; private int totalSubmissions; private boolean forGrade; private String password; private int numberRetake; private int actualNumberRetake; private HashMap itemContentsMap; // For paging private int partIndex; private int questionIndex; private boolean next_page; private boolean reload = true; // daisy added these for SelectActionListener private boolean notTakeable = true; private boolean pastDue; private long subTime; private long raw; private String takenHours; private String takenMinutes; private AssessmentGradingData adata; private PublishedAssessmentFacade publishedAssessment; private java.util.Date feedbackDate; private String feedbackDelivery; private String showScore; private boolean hasTimeLimit; private boolean isMoreThanOneQuestion; private Integer scoringType; // daisyf added for servlet Login.java, to support anonymous login with // publishedUrl private boolean anonymousLogin = false; private String contextPath; private boolean initAgentAccessString = false; // daisyf added this for timed assessment for SAK-6990, to check if mutiple windows were open // during timed assessment private String timerId = null; /** Use serialVersionUID for interoperability. */ private final static long serialVersionUID = -1090852048737428722L; private boolean showStudentScore; private boolean showStudentQuestionScore; // SAM-387 // esmiley added to track if timer has been started in timed assessments private boolean timeRunning; // SAM-535 // esmiley added to track JavaScript private String javaScriptEnabledCheck; //cwen private String siteId; private boolean beginAssessment; // this instance tracks if the Agent is taking a test via URL, as well as // current agent string (if assigned). SAK-1927: esmiley private AgentFacade deliveryAgent; private String display_dayDateFormat = ContextUtil.getLocalizedString( "org.sakaiproject.tool.assessment.bundle.GeneralMessages", "output_day_date_no_sec"); private SimpleDateFormat dayDisplayFormat = new SimpleDateFormat(display_dayDateFormat, new ResourceLoader().getLocale()); private String display_dateFormat = ContextUtil .getLocalizedString("org.sakaiproject.tool.assessment.bundle.GeneralMessages", "output_date_no_sec"); private SimpleDateFormat displayFormat = new SimpleDateFormat(display_dateFormat, new ResourceLoader().getLocale()); private boolean noQuestions = false; // this assessmentGradingId is used to generate seed in getSeed(...) of DeliveryActaionListener.java // We need this because the assessmentGradingData in delivery bean might not be the one we want to display // especially for review assessment and grade assessment. In other word, if student has started taking another assessment, // the assessmentGradingData in deliver bean will be the newly created one. Then, of course, the assessmentGradingId // will be the new id which is not what we want in review assessment or grade assessment private Long assessmentGradingId; private boolean fromTableOfContents; private int fileUploadSizeMax; private boolean studentRichText; private boolean isAnyInvalidFinInput; private String redrawAnchorName; // If set to true, delivery of the assessment is blocked. // This attribute is set to true if a secure delivery module is selected for the assessment and the security // check fails. If a module is selected, but is no longer installed or is disabled then the check is bypassed // and this attribute remains false. private boolean blockDelivery = false; // HTML fragment injected by the secure delivery module selected. Nothing is injected if no module has // been selected or if the selected module is no longer installed or disabled. private String secureDeliveryHTMLFragment; private boolean isFromPrint; private ExtendedTimeService extendedTimeService = null; private boolean showTimeWarning; private boolean hasShowTimeWarning; private boolean turnIntoTimedAssessment; private boolean useDueDate; private boolean submitFromTimeoutPopup; private boolean skipFlag; private Date deadline; private boolean firstTimeTaking; private static String ACCESSBASE = ServerConfigurationService.getAccessUrl(); private static String RECPATH = ServerConfigurationService.getString("samigo.recommendations.path"); private static ResourceBundle eventLogMessages = ResourceBundle .getBundle("org.sakaiproject.tool.assessment.bundle.EventLogMessages"); private static String EXTENDED_TIME_KEY = "extendedTime"; private static String questionProgressUnansweredPath = ServerConfigurationService .getString("samigo.questionprogress.unansweredpath", "/images/whiteBubble15.png"); private static String questionProgressAnsweredPath = ServerConfigurationService .getString("samigo.questionprogress.answeredpath", "/images/blackBubble15.png"); private static String questionProgressMardPath = ServerConfigurationService .getString("samigo.questionprogress.mardpath", "/images/questionMarkBubble15.png"); /** * Creates a new DeliveryBean object. */ public DeliveryBean() { deliveryAgent = new AgentFacade(); } /** * * * @return */ public int getItemIndex() { return this.itemIndex; } /** * * * @param itemIndex */ public void setItemIndex(int itemIndex) { this.itemIndex = itemIndex; } /** * * * @return */ public int getSize() { return this.size; } /** * * * @param size */ public void setSize(int size) { this.size = size; } /** * * * @return */ public String getAssessmentId() { return assessmentId; } /** * * * @param assessmentId */ public void setAssessmentId(String assessmentId) { this.assessmentId = assessmentId; } /** * * * @return */ public ArrayList getMarkedForReview() { return markedForReview; } /** * * * @param markedForReview */ public void setMarkedForReview(ArrayList markedForReview) { this.markedForReview = markedForReview; } /** * * * @return */ public boolean getReviewMarked() { return this.reviewMarked; } /** * * * @param reviewMarked */ public void setReviewMarked(boolean reviewMarked) { this.reviewMarked = reviewMarked; } /** * * * @return */ public boolean getReviewAll() { return this.reviewAll; } /** * * * @param reviewAll */ public void setReviewAll(boolean reviewAll) { this.reviewAll = reviewAll; } /** * * * @return */ public String getAction() { return this.action; } /** * * * @param action */ public void setAction(String action) { this.action = action; } /** * * * @return */ public Date getBeginTime() { return beginTime; } public String getBeginTimeString() { String beginTimeString = ""; if (beginTime == null) { return beginTimeString; } try { TimeUtil tu = new TimeUtil(); beginTimeString = tu.getDisplayDateTime(dayDisplayFormat, beginTime); } catch (Exception ex) { // we will leave it as an empty string log.warn("Unable to format date."); ex.printStackTrace(); } return beginTimeString; } /** * * * @param beginTime */ public void setBeginTime(Date beginTime) { this.beginTime = beginTime; } /** * * * @return */ public String getEndTime() { return endTime; } /** * * * @param endTime */ public void setEndTime(String endTime) { this.endTime = endTime; } /** * * * @return */ public String getCurrentTime() { return this.currentTime; } /** * * * @param currentTime */ public void setCurrentTime(String currentTime) { this.currentTime = currentTime; } /** * * * @return */ public String getMultipleAttempts() { return this.multipleAttempts; } /** * * * @param multipleAttempts */ public void setMultipleAttempts(String multipleAttempts) { this.multipleAttempts = multipleAttempts; } /** * * * @return */ public String getTimeOutSubmission() { return this.timeOutSubmission; } /** * * * @param timeOutSubmission */ public void setTimeOutSubmission(String timeOutSubmission) { this.timeOutSubmission = timeOutSubmission; } /** * * * @return */ public java.util.Date getSubmissionTime() { return submissionTime; } /** * * * @param submissionTime */ public void setSubmissionTime(java.util.Date submissionTime) { this.submissionTime = submissionTime; } /** * * * @return */ public String getTimeElapse() { return timeElapse; } /** * * * @param timeElapse */ public void setTimeElapse(String timeElapse) { try { if (timeElapse != null && !("").equals(timeElapse) && getTimeLimit() != null && !("").equals(getTimeLimit())) { double limit = (new Double(getTimeLimit())).doubleValue(); double elapsed = (new Double(timeElapse)).doubleValue(); if (limit > elapsed) this.timeElapse = timeElapse; else this.timeElapse = getTimeLimit(); setTimeElapseDouble((new Double(timeElapse)).doubleValue()); } } catch (Exception e) { log.warn("setTimeElapse error:" + e.getMessage()); } } /** * * * @return */ public String getSubmissionTicket() { return submissionTicket; } /** * * * @param submissionTicket */ public void setSubmissionTicket(String submissionTicket) { this.submissionTicket = submissionTicket; } /** * * * @return */ public int getDisplayIndex() { return this.itemIndex + 1; } /** * * * @return */ public String getUsername() { return username; } /** * * * @param username */ public void setUsername(String username) { this.username = username; } /** * * * @return */ public String getAssessmentTitle() { return assessmentTitle; } /** * * * @param assessmentTitle */ public void setAssessmentTitle(String assessmentTitle) { this.assessmentTitle = assessmentTitle; } /** * * * @return */ public ArrayList getBlankItems() { return this.blankItems; } /** * * * @param blankItems */ public void setBlankItems(ArrayList blankItems) { this.blankItems = blankItems; } /** * * * @return */ public boolean getReviewBlank() { return reviewBlank; } /** * * * @param reviewBlank */ public void setReviewBlank(boolean reviewBlank) { this.reviewBlank = reviewBlank; } /** * * * @return */ public ArrayList getMarkedForReviewIdents() { return markedForReviewIdents; } /** * * * @param markedForReviewIdents */ public void setMarkedForReviewIdents(ArrayList markedForReviewIdents) { this.markedForReviewIdents = markedForReviewIdents; } /** * * * @return */ public ArrayList getBlankItemIdents() { return blankItemIdents; } /** * * * @param blankItemIdents */ public void setBlankItemIdents(ArrayList blankItemIdents) { this.blankItemIdents = blankItemIdents; } /** * * * @return */ public int getSectionIndex() { return this.sectionIndex; } /** * * * @param sectionIndex */ public void setSectionIndex(int sectionIndex) { this.sectionIndex = sectionIndex; } /** * * * @return */ public boolean getPrevious() { return previous; } /** * * * @param previous */ public void setPrevious(boolean previous) { this.previous = previous; } //Settings public String getQuestionLayout() { if (getSettings().isFormatByQuestion()) { questionLayout = "1"; } else if (getSettings().isFormatByPart()) { questionLayout = "2"; } else if (getSettings().isFormatByAssessment()) { questionLayout = "3"; } return questionLayout; } /** * * * @param questionLayout */ public void setQuestionLayout(String questionLayout) { this.questionLayout = questionLayout; } /** * * * @return */ public String getNavigation() { return navigation; } /** * * * @param navigation */ public void setNavigation(String navigation) { this.navigation = navigation; } /** * * * @return */ public String getNumbering() { return numbering; } /** * * * @param numbering */ public void setNumbering(String numbering) { this.numbering = numbering; } /** * * * @return */ public String getFeedback() { return feedback; } /** * * * @param feedback */ public void setFeedback(String feedback) { this.feedback = feedback; } /** * * * @return */ public String getNoFeedback() { return noFeedback; } /** * * * @param noFeedback */ public void setNoFeedback(String noFeedback) { this.noFeedback = noFeedback; } /** * * * @return */ public String getStatistics() { return statistics; } /** * * * @param statistics */ public void setStatistics(String statistics) { this.statistics = statistics; } /** * * * @return */ public FeedbackComponent getFeedbackComponent() { return feedbackComponent; } /** * * * @param feedbackComponent */ public void setFeedbackComponent(FeedbackComponent feedbackComponent) { this.feedbackComponent = feedbackComponent; } public String getFeedbackComponentOption() { return feedbackComponentOption; } public void setFeedbackComponentOption(String feedbackComponentOption) { this.feedbackComponentOption = feedbackComponentOption; } /** * Types of feedback in FeedbackComponent: * * SHOW CORRECT SCORE * SHOW STUDENT SCORE * SHOW ITEM LEVEL * SHOW SECTION LEVEL * SHOW GRADER COMMENT * SHOW STATS * SHOW QUESTION * SHOW RESPONSE **/ /** * @return */ public SettingsDeliveryBean getSettings() { // SAM-1438 - We occasionally see the settings bean as null during // submission, within a JSF phase of deliverAssessment.jsp but it is // generally not reproducible. This block protects against the bug // by loading up the settings for this assessment. They are not assigned // to the local settings variable as to avoid changing any more behavior // than is needed. This is effectively a failsafe and diagnostic that // should not really be necessary. if (settings == null) { Session session = SessionManager.getCurrentSession(); StringBuilder sb = new StringBuilder(400); sb.append("SAM-1438 - Delivery settings bean is null.\n"); if (session != null) { sb.append(" - User EID : ").append(session.getUserEid()).append("\n"); sb.append(" - User ID : ").append(session.getUserId()).append("\n"); sb.append(" - Session ID: ").append(session.getId()).append("\n"); } else { sb.append(" - Session is null. Cannot determine user.\n"); } sb.append(" - Published Assessment ID: "); if (publishedAssessment == null) { sb.append("<null>\n"); } else { sb.append(publishedAssessment.getPublishedAssessmentId()).append("\n"); sb.append(" - Assessment Title : ").append(publishedAssessment.getTitle()) .append("\n"); sb.append(" - Assessment Site ID : ").append(publishedAssessment.getOwnerSiteId()); BeginDeliveryActionListener listener = new BeginDeliveryActionListener(); //settings variable may be populated by populateBeanFromPub listener.populateBeanFromPub(this, publishedAssessment); } log.warn(sb.toString()); } //If the settings is still null, need to return something if (settings == null) { log.warn("SAM-2410 - Delivery settings bean is still null, returning an empty bean"); SettingsDeliveryBean tempSettings = new SettingsDeliveryBean(); return tempSettings; } return settings; } /** * @param bean */ public void setSettings(SettingsDeliveryBean settings) { this.settings = settings; } /** * @return */ public String getErrorMessage() { return errorMessage; } /** * @param string */ public void setErrorMessage(String string) { errorMessage = string; } /** * @return */ public String getDuration() { return duration; } /** * @param string */ public void setDuration(String string) { duration = string; } /** * @return */ public String getCreatorName() { return creatorName; } /** * @param string */ public void setCreatorName(String string) { creatorName = string; } public java.util.Date getDueDate() { return dueDate; } public String getDueDateString() { String dateString = ""; if (dueDate == null) { return dateString; } try { TimeUtil tu = new TimeUtil(); dateString = tu.getDisplayDateTime(displayFormat, dueDate); } catch (Exception ex) { // we will leave it as an empty string log.warn("Unable to format date."); ex.printStackTrace(); } return dateString; } public String getDayDueDateString() { String dateString = ""; if (dueDate == null) { return dateString; } try { TimeUtil tu = new TimeUtil(); dateString = tu.getDisplayDateTime(dayDisplayFormat, dueDate); } catch (Exception ex) { // we will leave it as an empty string log.warn("Unable to format date."); ex.printStackTrace(); } return dateString; } public void setDueDate(java.util.Date dueDate) { this.dueDate = dueDate; } public Date getAdjustedTimedAssesmentDueDate() { return adjustedTimedAssesmentDueDate; } public String getAdjustedTimedAssesmentDueDateString() { String adjustedTimedAssesmentDueDateString = ""; if (adjustedTimedAssesmentDueDate == null) { return adjustedTimedAssesmentDueDateString; } try { TimeUtil tu = new TimeUtil(); adjustedTimedAssesmentDueDateString = tu.getDisplayDateTime(dayDisplayFormat, adjustedTimedAssesmentDueDate); } catch (Exception ex) { // we will leave it as an empty string log.warn("Unable to format date."); ex.printStackTrace(); } return adjustedTimedAssesmentDueDateString; } public void setAdjustedTimedAssesmentDueDate(Date adjustedTimedAssesmentDueDate) { this.adjustedTimedAssesmentDueDate = adjustedTimedAssesmentDueDate; } public java.util.Date getRetractDate() { return retractDate; } public String getDayRetractDateString() { String dateString = ""; if (retractDate == null) { return dateString; } try { TimeUtil tu = new TimeUtil(); dateString = tu.getDisplayDateTime(dayDisplayFormat, retractDate); } catch (Exception ex) { // we will leave it as an empty string log.warn("Unable to format date."); ex.printStackTrace(); } return dateString; } public void setRetractDate(java.util.Date retractDate) { this.retractDate = retractDate; } public boolean isStatsAvailable() { return statsAvailable; } public void setStatsAvailable(boolean statsAvailable) { this.statsAvailable = statsAvailable; } public boolean isSubmitted() { return submitted; } public void setSubmitted(boolean submitted) { this.submitted = submitted; } public boolean isGraded() { return graded; } public void setGraded(boolean graded) { this.graded = graded; } public boolean getFeedbackOnDate() { return feedbackOnDate; } public void setFeedbackOnDate(boolean feedbackOnDate) { this.feedbackOnDate = feedbackOnDate; } public String getGraderComment() { if (graderComment == null) { return ""; } return graderComment; } public void setGraderComment(String newComment) { graderComment = newComment; } public String getRawScore() { return rawScore; } public String getRoundedRawScore() { try { String newscore = ContextUtil.getRoundedValue(rawScore, 2); return Validator.check(newscore, "N/A"); } catch (Exception e) { // encountered some weird number format/locale return Validator.check(rawScore, "0"); } } public String getRoundedRawScoreViaURL() { if (adata.getFinalScore() != null) { rawScore = adata.getFinalScore().toString(); } else { rawScore = "0"; } try { String newscore = ContextUtil.getRoundedValue(rawScore, 2); return Validator.check(newscore, "N/A"); } catch (Exception e) { // encountered some weird number format/locale return Validator.check(rawScore, "0"); } } public void setRawScore(String rawScore) { this.rawScore = rawScore; } public long getRaw() { return raw; } public void setRaw(long raw) { this.raw = raw; } public String getGrade() { return grade; } public void setGrade(String grade) { this.grade = grade; } public java.util.Date getSubmissionDate() { return submissionDate; } public String getSubmissionDateString() { String dateString = ""; if (submissionDate == null) { return dateString; } try { TimeUtil tu = new TimeUtil(); dateString = tu.getDisplayDateTime(displayFormat, submissionDate); } catch (Exception ex) { // we will leave it as an empty string log.warn("Unable to format date."); ex.printStackTrace(); } return dateString; } public void setSubmissionDate(java.util.Date submissionDate) { this.submissionDate = submissionDate; } public String getImage() { return image; } public void setImage(String image) { this.image = image; } public boolean isHasImage() { return hasImage; } public void setHasImage(boolean hasImage) { this.hasImage = hasImage; } public String getInstructorMessage() { return instructorMessage; } public void setInstructorMessage(String instructorMessage) { this.instructorMessage = instructorMessage; } public String getCourseName() { return courseName; } public void setCourseName(String courseName) { this.courseName = courseName; } public String getTimeLimit() { return timeLimit; } public void setTimeLimit(String timeLimit) { this.timeLimit = timeLimit; } public int getTimeLimit_hour() { return timeLimit_hour; } public void setTimeLimit_hour(int timeLimit_hour) { this.timeLimit_hour = timeLimit_hour; } public int getTimeLimit_minute() { return timeLimit_minute; } public void setTimeLimit_minute(int timeLimit_minute) { this.timeLimit_minute = timeLimit_minute; } public String getTimeLimitString() { return timeLimitString; } public void setTimeLimitString(String timeLimitString) { this.timeLimitString = timeLimitString; } /** * Bean with table of contents information and * a list of all the sections in the assessment * which in turn has a list of all the item contents. * @return table of contents */ public ContentsDeliveryBean getTableOfContents() { return tableOfContents; } /** * Bean with table of contents information and * a list of all the sections in the assessment * which in turn has a list of all the item contents. * @param tableOfContents table of contents */ public void setTableOfContents(ContentsDeliveryBean tableOfContents) { this.tableOfContents = tableOfContents; } /** * Bean with a list of all the sections in the current page * which in turn has a list of all the item contents for the page. * * This is like the table of contents, but the selections are restricted to * that on one page. * * Since these properties are on a page delivery basis--if: * 1. there is only one item per page the list of items will * contain one item and the list of parts will return one part, or if-- * * 2. there is one part per page the list of items will be that * for that part only and there will only be one part, however-- * * 3. if it is all parts and items on a single page there * will be a list of all parts and items. * * @return ContentsDeliveryBean */ public ContentsDeliveryBean getPageContents() { return pageContents; } /** * Bean with a list of all the sections in the current page * which in turn has a list of all the item contents for the page. * * Since these properties are on a page delivery basis--if: * 1. there is only one item per page the list of items will * contain one item and the list of parts will return one part, or if-- * * 2. there is one part per page the list of items will be that * for that part only and there will only be one part, however-- * * 3. if it is all parts and items on a single page there * will be a list of all parts and items. * * @param pageContents ContentsDeliveryBean */ public void setPageContents(ContentsDeliveryBean pageContents) { this.pageContents = pageContents; } public String getSubmissionId() { return submissionId; } public void setSubmissionId(String submissionId) { this.submissionId = submissionId; } public String getSubmissionMessage() { return submissionMessage; } public void setSubmissionMessage(String submissionMessage) { this.submissionMessage = submissionMessage; } public int getSubmissionsRemaining() { return submissionsRemaining; } public void setSubmissionsRemaining(int submissionsRemaining) { this.submissionsRemaining = submissionsRemaining; } public int getTotalSubmissions() { return totalSubmissions; } public void setTotalSubmissions(int totalSubmissions) { this.totalSubmissions = totalSubmissions; } public String getInstructorName() { return instructorName; } public void setInstructorName(String instructorName) { this.instructorName = instructorName; } public boolean getForGrade() { return forGrade; } public void setForGrade(boolean newfor) { forGrade = newfor; } public String submitForGradeFromTimer() { return submitForGrade(true, false); } public String submitForGrade() { return submitForGrade(false, false); } public String submitFromTimeoutPopup() { return submitForGrade(false, true); } private String submitForGrade(boolean isFromTimer, boolean submitFromTimeoutPopup) { try { if (this.actionMode == PREVIEW_ASSESSMENT) { return "editAssessment"; } EventTrackingService.post(EventTrackingService.newEvent("sam.assessment.submit.click_sub", "siteId=" + AgentFacade.getCurrentSiteId() + ", submissionId=" + adata.getAssessmentGradingId(), siteId, true, NotificationService.NOTI_REQUIRED)); if (!submitFromTimeoutPopup) { String nextAction = checkBeforeProceed(true, isFromTimer); log.debug("***** next Action=" + nextAction); EventTrackingService.post(EventTrackingService.newEvent( "sam.assessment.submit.checked", "siteId=" + AgentFacade.getCurrentSiteId() + ", submissionId=" + adata.getAssessmentGradingId(), siteId, true, NotificationService.NOTI_REQUIRED)); if (!("safeToProceed").equals(nextAction)) { return nextAction; } } setForGrade(true); if (submitFromTimeoutPopup) { setSubmitFromTimeoutPopup(true); } else { setSubmitFromTimeoutPopup(false); } SessionUtil.setSessionTimeout(FacesContext.getCurrentInstance(), this, false); syncTimeElapsedWithServer(); SubmitToGradingActionListener listener = new SubmitToGradingActionListener(); // submission remaining and totalSubmissionPerAssessmentHash is updated inside // SubmitToGradingListener try { listener.processAction(null); } catch (FinFormatException e) { log.debug(e.getMessage()); return "takeAssessment"; } catch (SaLengthException sae) { log.debug(sae.getMessage()); return "takeAssessment"; } // We don't need to call completeItemGradingData to create new ItemGradingData for linear access // because each ItemGradingData is created when it is viewed/answered if (!"1".equals(navigation)) { GradingService gradingService = new GradingService(); gradingService.completeItemGradingData(adata); } String returnValue = "submitAssessment"; if (!isFromTimer) { if (this.actionMode == TAKE_ASSESSMENT_VIA_URL) // this is for accessing via published url { returnValue = "anonymousThankYou"; PublishedAssessmentService publishedAssessmentService = new PublishedAssessmentService(); String siteId = publishedAssessmentService .getPublishedAssessmentOwner(adata.getPublishedAssessmentId()); EventTrackingService.post(EventTrackingService.newEvent("sam.assessment.submit.via_url", "siteId=" + AgentFacade.getCurrentSiteId() + ", submissionId=" + adata.getAssessmentGradingId(), siteId, true, NotificationService.NOTI_REQUIRED)); } else { EventTrackingService.post(EventTrackingService.newEvent("sam.assessment.submit", "siteId=" + AgentFacade.getCurrentSiteId() + ", submissionId=" + adata.getAssessmentGradingId(), true)); } } else { if (this.actionMode == TAKE_ASSESSMENT_VIA_URL) // this is for accessing via published url { returnValue = "anonymousThankYou"; PublishedAssessmentService publishedAssessmentService = new PublishedAssessmentService(); String siteId = publishedAssessmentService .getPublishedAssessmentOwner(adata.getPublishedAssessmentId()); EventTrackingService.post(EventTrackingService.newEvent("sam.assessment.timer_submit.url", "siteId=" + AgentFacade.getCurrentSiteId() + ", submissionId=" + adata.getAssessmentGradingId(), siteId, true, NotificationService.NOTI_REQUIRED)); } else { EventTrackingService.post(EventTrackingService.newEvent("sam.assessment.timer_submit", "siteId=" + AgentFacade.getCurrentSiteId() + ", submissionId=" + adata.getAssessmentGradingId(), true)); } } forGrade = false; SelectActionListener l2 = new SelectActionListener(); l2.processAction(null); reload = true; // finish within time limit, clean timedAssessment from queue removeTimedAssessmentFromQueue(); // finish secure delivery setSecureDeliveryHTMLFragment(""); setBlockDelivery(false); SecureDeliveryServiceAPI secureDelivery = SamigoApiFactory.getInstance().getSecureDeliveryServiceAPI(); if (secureDelivery.isSecureDeliveryAvaliable()) { String moduleId = publishedAssessment .getAssessmentMetaDataByLabel(SecureDeliveryServiceAPI.MODULE_KEY); if (moduleId != null && !SecureDeliveryServiceAPI.NONE_ID.equals(moduleId)) { HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance() .getExternalContext().getRequest(); PhaseStatus status = secureDelivery.validatePhase(moduleId, Phase.ASSESSMENT_FINISH, publishedAssessment, request); setSecureDeliveryHTMLFragment(secureDelivery.getHTMLFragment(moduleId, publishedAssessment, request, Phase.ASSESSMENT_FINISH, status, new ResourceLoader().getLocale())); } } EventLogService eventService = new EventLogService(); EventLogFacade eventLogFacade = new EventLogFacade(); List eventLogDataList = eventService.getEventLogData(adata.getAssessmentGradingId()); EventLogData eventLogData = (EventLogData) eventLogDataList.get(0); eventLogData.setErrorMsg(eventLogMessages.getString("no_error")); Date endDate = new Date(); eventLogData.setEndDate(endDate); if (endDate != null && eventLogData.getStartDate() != null) { double minute = 1000 * 60; int eclipseTime = (int) Math .ceil(((endDate.getTime() - eventLogData.getStartDate().getTime()) / minute)); eventLogData.setEclipseTime(Integer.valueOf(eclipseTime)); } else { eventLogData.setEclipseTime(null); eventLogData.setErrorMsg(eventLogMessages.getString("error_take")); } eventLogFacade.setData(eventLogData); eventService.saveOrUpdateEventLog(eventLogFacade); return returnValue; } catch (Exception e) { EventLogService eventService = new EventLogService(); EventLogFacade eventLogFacade = new EventLogFacade(); EventLogData eventLogData = null; List eventLogDataList = eventService.getEventLogData(adata.getAssessmentGradingId()); if (eventLogDataList != null && eventLogDataList.size() > 0) { eventLogData = (EventLogData) eventLogDataList.get(0); eventLogData.setErrorMsg(eventLogMessages.getString("error_submit")); eventLogData.setEndDate(new Date()); } eventLogFacade.setData(eventLogData); eventService.saveOrUpdateEventLog(eventLogFacade); return null; } } public String confirmSubmit() { if (this.actionMode == TAKE_ASSESSMENT || this.actionMode == TAKE_ASSESSMENT_VIA_URL) { if (adata != null) { EventTrackingService .post(EventTrackingService.newEvent("sam.submit.from_last_page", "siteId=" + AgentFacade.getCurrentSiteId() + ", submissionId=" + adata.getAssessmentGradingId(), siteId, true, NotificationService.NOTI_REQUIRED)); } else { EventTrackingService.post(EventTrackingService.newEvent("sam.submit.from_last_page", "siteId=" + AgentFacade.getCurrentSiteId() + ", adata is null", siteId, true, NotificationService.NOTI_REQUIRED)); } } String nextAction = checkBeforeProceed(); log.debug("***** next Action=" + nextAction); if (!("safeToProceed").equals(nextAction)) { return nextAction; } setForGrade(false); if (this.actionMode == TAKE_ASSESSMENT || this.actionMode == TAKE_ASSESSMENT_VIA_URL) { syncTimeElapsedWithServer(); SubmitToGradingActionListener listener = new SubmitToGradingActionListener(); try { listener.processAction(null); } catch (FinFormatException fine) { log.debug(fine.getMessage()); return "takeAssessment"; } catch (SaLengthException sae) { log.debug(sae.getMessage()); return "takeAssessment"; } } skipFlag = true; DeliveryActionListener l2 = new DeliveryActionListener(); l2.processAction(null); return "confirmsubmit"; } public String confirmSubmitTOC() { if (this.actionMode == TAKE_ASSESSMENT || this.actionMode == TAKE_ASSESSMENT_VIA_URL) { if (adata != null) { EventTrackingService .post(EventTrackingService.newEvent("sam.submit.from_toc", "siteId=" + AgentFacade.getCurrentSiteId() + ", submissionId=" + adata.getAssessmentGradingId(), siteId, true, NotificationService.NOTI_REQUIRED)); } else { EventTrackingService.post(EventTrackingService.newEvent("sam.submit.from_toc", "siteId=" + AgentFacade.getCurrentSiteId() + ", adata is null", siteId, true, NotificationService.NOTI_REQUIRED)); } } String nextAction = checkBeforeProceed(); log.debug("***** next Action=" + nextAction); if (!("safeToProceed").equals(nextAction)) { return nextAction; } setForGrade(false); if (this.actionMode == TAKE_ASSESSMENT || this.actionMode == TAKE_ASSESSMENT_VIA_URL) { syncTimeElapsedWithServer(); SubmitToGradingActionListener listener = new SubmitToGradingActionListener(); try { listener.processAction(null); } catch (FinFormatException e) { log.debug(e.getMessage()); return "takeAssessment"; } catch (SaLengthException sae) { log.debug(sae.getMessage()); return "takeAssessment"; } } setFromTableOfContents(true); DeliveryActionListener l2 = new DeliveryActionListener(); l2.processAction(null); setContinue(false); return "confirmsubmit"; } public String saveAndExit() { return saveAndExit(true); } public String saveNoCheck() { return saveAndExit(false); } public String saveAndExit(boolean needToCheck) { if (needToCheck) { String nextAction = checkBeforeProceed(); log.debug("***** next Action=" + nextAction); if (!("safeToProceed").equals(nextAction)) { return nextAction; } } FacesContext context = FacesContext.getCurrentInstance(); SessionUtil.setSessionTimeout(context, this, false); log.debug("***DeliverBean.saveAndEXit face context =" + context); forGrade = false; if (this.actionMode == TAKE_ASSESSMENT || this.actionMode == TAKE_ASSESSMENT_VIA_URL) { syncTimeElapsedWithServer(); SubmitToGradingActionListener listener = new SubmitToGradingActionListener(); try { listener.processAction(null); } catch (FinFormatException e) { log.debug(e.getMessage()); return "takeAssessment"; } catch (SaLengthException sae) { log.debug(sae.getMessage()); return "takeAssessment"; } } String returnValue = "saveForLaterWarning"; if (needToCheck) { if (this.actionMode == TAKE_ASSESSMENT_VIA_URL) { returnValue = "anonymousQuit"; } else { returnValue = "saveForLaterWarning"; } } else { if (this.actionMode == TAKE_ASSESSMENT_VIA_URL) { returnValue = "notSubmitted"; } else { returnValue = "select"; } } SelectActionListener l2 = new SelectActionListener(); l2.processAction(null); reload = true; // quit within time limit, clean timedAssessment from queue, // removeTimedAssessmentFromQueue(); return returnValue; } public String next_page() { return next_helper(false); } public String goto_question() { return next_helper(true); } private String next_helper(boolean isGoToQuestion) { String nextAction = checkBeforeProceed(); log.debug("***** next Action=" + nextAction); if (!("safeToProceed").equals(nextAction)) { return nextAction; } forGrade = false; if (this.actionMode == TAKE_ASSESSMENT || this.actionMode == TAKE_ASSESSMENT_VIA_URL) { syncTimeElapsedWithServer(); SubmitToGradingActionListener listener = new SubmitToGradingActionListener(); try { listener.processAction(null); } catch (FinFormatException e) { log.debug(e.getMessage()); return "takeAssessment"; } catch (SaLengthException sae) { log.debug(sae.getMessage()); return "takeAssessment"; } } int oPartIndex = partIndex; int oQuestionIndex = questionIndex; if (getSettings().isFormatByPart()) { String partIndexString = (String) FacesContext.getCurrentInstance().getExternalContext() .getRequestParameterMap().get("partnumber"); if (isGoToQuestion) { partIndex = Integer.parseInt(partIndexString); } else { partIndex++; } } if (getSettings().isFormatByQuestion()) { String questionIndexString = (String) FacesContext.getCurrentInstance().getExternalContext() .getRequestParameterMap().get("questionnumber"); if (isGoToQuestion) { questionIndex = Integer.parseInt(questionIndexString); } else { questionIndex++; } } DeliveryActionListener l2 = new DeliveryActionListener(); l2.processAction(null); if ("1".equals(navigation) && this.actionMode != PREVIEW_ASSESSMENT) { LinearAccessDeliveryActionListener linearAccessDeliveryActionListener = new LinearAccessDeliveryActionListener(); if (isGoToQuestion) { linearAccessDeliveryActionListener.saveLastVisitedPosition(this, oPartIndex, oQuestionIndex); } else { linearAccessDeliveryActionListener.saveLastVisitedPosition(this, partIndex, questionIndex); } } reload = false; return "takeAssessment"; } public String same_page() { String nextAction = checkBeforeProceed(); log.debug("***** next Action=" + nextAction); if (!("safeToProceed").equals(nextAction)) { return nextAction; } forGrade = false; if (this.actionMode == TAKE_ASSESSMENT || this.actionMode == TAKE_ASSESSMENT_VIA_URL) { syncTimeElapsedWithServer(); SubmitToGradingActionListener listener = new SubmitToGradingActionListener(); try { listener.processAction(null); } catch (FinFormatException e) { log.debug(e.getMessage()); return "takeAssessment"; } catch (SaLengthException sae) { log.debug(sae.getMessage()); return "takeAssessment"; } } DeliveryActionListener l2 = new DeliveryActionListener(); l2.processAction(null); reload = false; return "takeAssessment"; } public String auto_save() { skipFlag = true; return save_work(); } public String save_work() { String nextAction = checkBeforeProceed(); log.debug("***** next Action=" + nextAction); if (!("safeToProceed").equals(nextAction)) { return nextAction; } forGrade = false; if (this.actionMode == TAKE_ASSESSMENT || this.actionMode == TAKE_ASSESSMENT_VIA_URL) { syncTimeElapsedWithServer(); SubmitToGradingActionListener listener = new SubmitToGradingActionListener(); try { listener.processAction(null); } catch (FinFormatException e) { log.debug(e.getMessage()); return "takeAssessment"; } catch (SaLengthException sae) { log.debug(sae.getMessage()); return "takeAssessment"; } } DeliveryActionListener l2 = new DeliveryActionListener(); l2.processAction(null); reload = false; return "takeAssessment"; } public String previous() { String nextAction = checkBeforeProceed(); log.debug("***** next Action=" + nextAction); if (!("safeToProceed").equals(nextAction)) { return nextAction; } if (getSettings().isFormatByPart()) { partIndex--; questionIndex = 0; } if (getSettings().isFormatByQuestion()) { questionIndex--; } forGrade = false; if (this.actionMode == TAKE_ASSESSMENT || this.actionMode == TAKE_ASSESSMENT_VIA_URL) { syncTimeElapsedWithServer(); SubmitToGradingActionListener listener = new SubmitToGradingActionListener(); try { listener.processAction(null); } catch (FinFormatException e) { log.debug(e.getMessage()); return "takeAssessment"; } catch (SaLengthException sae) { log.debug(sae.getMessage()); return "takeAssessment"; } } DeliveryActionListener l2 = new DeliveryActionListener(); l2.processAction(null); reload = false; return "takeAssessment"; } public String confirmSubmitPrevious() { String nextAction = checkBeforeProceed(); log.debug("***** next Action=" + nextAction); if (!("safeToProceed").equals(nextAction)) { return nextAction; } forGrade = false; syncTimeElapsedWithServer(); DeliveryActionListener l2 = new DeliveryActionListener(); l2.processAction(null); return "takeAssessment"; } // this is the PublishedAccessControl.finalPageUrl public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getConfirmation() { return confirmation; } public void setConfirmation(String confirmation) { this.confirmation = confirmation; } /** * if required, assessment password * @return password */ public String getPassword() { return password; } /** * if required, assessment password * @param string assessment password */ public void setPassword(String string) { password = string; } public String validatePassword() { log.debug("**** username=" + username); log.debug("**** password=" + password); log.debug("**** setting username=" + getSettings().getUsername()); log.debug("**** setting password=" + getSettings().getPassword()); if (password == null && username == null) { return "passwordAccessError"; } if (!"".equals(getSettings().getUsername())) { if (username != null && !username.equals(getSettings().getUsername())) { return "passwordAccessError"; } } if (!"".equals(getSettings().getPassword())) { if (password != null && !password.equals(getSettings().getPassword())) { return "passwordAccessError"; } } // in post 2.1, clicking at Begin Assessment takes users to the // 1st question. return "takeAssessment"; } public String validateIP() { String thisIp = ((javax.servlet.http.HttpServletRequest) FacesContext.getCurrentInstance() .getExternalContext().getRequest()).getRemoteAddr(); Iterator addresses = getSettings().getIpAddresses().iterator(); while (addresses.hasNext()) { String next = ((PublishedSecuredIPAddress) addresses.next()).getIpAddress(); if (next != null && next.indexOf("*") > -1) { next = next.substring(0, next.indexOf("*")); } if (next == null || next.trim().equals("") || thisIp.trim().startsWith(next.trim())) { // in post 2.1, clicking at Begin Assessment takes users to the // 1st question. return "takeAssessment"; } } return "ipAccessError"; } public String validate() { try { String results = "takeAssessment"; EventLogService eventService = new EventLogService(); EventLogFacade eventLogFacade = new EventLogFacade(); EventLogData eventLogData = new EventLogData(); // #1. check password if (!getSettings().getUsername().equals("") || !getSettings().getPassword().equals("")) { results = validatePassword(); log.debug("*** checked password=" + results); if ("passwordAccessError".equals(results)) { updatEventLog("error_pw_access"); } } // #2. check IP if (!"passwordAccessError".equals(results) && getSettings().getIpAddresses() != null && !getSettings().getIpAddresses().isEmpty()) { results = validateIP(); log.debug("*** checked password & IP=" + results); if (("ipAccessError").equals(results)) { updatEventLog("error_ip_access"); } } // secure delivery START phase // should occur before timer check, so that timer will be stopped if access is denied setSecureDeliveryHTMLFragment(""); setBlockDelivery(false); SecureDeliveryServiceAPI secureDelivery = SamigoApiFactory.getInstance().getSecureDeliveryServiceAPI(); if ("takeAssessment".equals(results) && secureDelivery.isSecureDeliveryAvaliable()) { String moduleId = publishedAssessment .getAssessmentMetaDataByLabel(SecureDeliveryServiceAPI.MODULE_KEY); if (moduleId != null && !SecureDeliveryServiceAPI.NONE_ID.equals(moduleId)) { HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance() .getExternalContext().getRequest(); PhaseStatus status = secureDelivery.validatePhase(moduleId, Phase.ASSESSMENT_START, publishedAssessment, request); setSecureDeliveryHTMLFragment(secureDelivery.getHTMLFragment(moduleId, publishedAssessment, request, Phase.ASSESSMENT_START, status, new ResourceLoader().getLocale())); setBlockDelivery(PhaseStatus.FAILURE == status); if (PhaseStatus.SUCCESS == status) { results = "takeAssessment"; } else { results = "secureDeliveryError"; updatEventLog("error_secure_delivery"); } } } // if results != "takeAssessment", stop the clock if it is a timed assessment // Trouble was the timer was started by DeliveryActionListener before validate() is being run. // So, we need to remove the timer thread as soon as we realized that the validation fails. if (!("takeAssessment".equals(results)) && adata != null) { TimedAssessmentQueue queue = TimedAssessmentQueue.getInstance(); TimedAssessmentGradingModel timedAG = (TimedAssessmentGradingModel) queue .get(adata.getAssessmentGradingId()); if (timedAG != null) { String agTimerId = timedAG.getTimerId(); if (agTimerId != null && agTimerId.equals(timerId)) { // SAK-6990: it is only safe to removed if u are sure that timedAG is started by your beginAssessment.jsp // we added a hidden field timerId on beginAssessment.jsp. Upon successful security check, a timedAG // will be created that carried this timerId. If user open another browser to take the same timed // assessment. If the security check of the new one fails, it won't stop the clock for existing one. queue.remove(timedAG); timeRunning = false; } } return results; } // check before proceed String nextAction = checkBeforeProceed(); log.debug("***** next Action=" + nextAction); if (!("safeToProceed").equals(nextAction)) { return nextAction; } // #3. results="" => no security checking required if ("".equals(results)) { // in post 2.1, clicking at Begin Assessment takes users to the // 1st question. return "takeAssessment"; } return results; } catch (Exception e) { log.error("accessError" + e.getMessage()); EventLogService eventService = new EventLogService(); EventLogFacade eventLogFacade = new EventLogFacade(); EventLogData eventLogData = null; List eventLogDataList = eventService.getEventLogData(adata.getAssessmentGradingId()); if (eventLogDataList != null && eventLogDataList.size() > 0) { eventLogData = (EventLogData) eventLogDataList.get(0); eventLogData.setErrorMsg(eventLogMessages.getString("error_access")); } eventLogFacade.setData(eventLogData); eventService.saveOrUpdateEventLog(eventLogFacade); return "accessError"; } } public void updatEventLog(String errorMsg) { EventLogService eventService = new EventLogService(); EventLogFacade eventLogFacade = new EventLogFacade(); EventLogData eventLogData = new EventLogData(); eventLogData.setAssessmentId(publishedAssessment.getPublishedAssessmentId()); eventLogData.setStartDate(new Date()); String agentEid = AgentFacade.getEid(); //ONC-3500 if (agentEid == null || "".equals(agentEid)) { agentEid = "N/A"; } eventLogData.setUserEid(agentEid); eventLogData.setTitle(publishedAssessment.getTitle()); String site_id = AgentFacade.getCurrentSiteId(); if (site_id == null) { //take assessment via url PublishedAssessmentService publishedAssessmentService = new PublishedAssessmentService(); site_id = publishedAssessmentService .getPublishedAssessmentOwner(publishedAssessment.getPublishedAssessmentId()); } eventLogData.setSiteId(site_id); eventLogData.setProcessId(null); eventLogData.setEndDate(null); eventLogData.setEclipseTime(null); eventLogData.setErrorMsg(eventLogMessages.getString(errorMsg)); eventLogFacade.setData(eventLogData); eventService.saveOrUpdateEventLog(eventLogFacade); } public String pvalidate() { // in post 2.1, clicking at Begin Assessment takes users to the // 1st question. return "takeAssessment"; } // Skipped paging methods public int getPartIndex() { return partIndex; } public void setPartIndex(int newindex) { partIndex = newindex; } public int getQuestionIndex() { return questionIndex; } public void setQuestionIndex(int newindex) { questionIndex = newindex; } public boolean getDoContinue() { return next_page; } public void setContinue(boolean docontinue) { next_page = docontinue; } public boolean getReload() { return reload; } public void setReload(boolean doreload) { reload = doreload; } // Store for paging public AssessmentGradingData getAssessmentGrading() { return adata; } public void setAssessmentGrading(AssessmentGradingData newdata) { adata = newdata; } private byte[] getMediaStream(String mediaLocation) { FileInputStream mediaStream = null; FileInputStream mediaStream2 = null; byte[] mediaByte = new byte[0]; try { int i; int size = 0; mediaStream = new FileInputStream(mediaLocation); if (mediaStream != null) { while ((i = mediaStream.read()) != -1) { size++; } } mediaStream2 = new FileInputStream(mediaLocation); mediaByte = new byte[size]; if (mediaStream2 != null) { mediaStream2.read(mediaByte, 0, size); } } catch (FileNotFoundException ex) { log.error("file not found=" + ex.getMessage()); } catch (IOException ex) { log.error("io exception=" + ex.getMessage()); } finally { if (mediaStream != null) { try { mediaStream.close(); } catch (IOException ex1) { log.warn(ex1.getMessage()); } } if (mediaStream2 != null) { try { mediaStream2.close(); } catch (IOException ex1) { log.warn(ex1.getMessage()); } } } return mediaByte; } /** * This method is used by jsf/delivery/deliveryFileUpload.jsp * <corejsf:upload * target="/jsf/upload_tmp/assessment#{delivery.assessmentId}/ * question#{question.itemData.itemId}/admin" * valueChangeListener="#{delivery.addMediaToItemGrading}" /> */ public void addMediaToItemGrading(javax.faces.event.ValueChangeEvent e) { if (isTimeRunning() && timeExpired()) setOutcome("timeExpired"); String mediaLocation = (String) e.getNewValue(); String action = addMediaToItemGrading(mediaLocation); syncTimeElapsedWithServer(); log.debug("****time passed after fileupload before loading of next question" + getTimeElapse()); setTimeElapseAfterFileUpload(getTimeElapse()); setOutcome(action); } /** * This method is used by jsf/delivery/deliverAudioRecording.jsp and * is called by addMediaToItemGrading(javax.faces.event.ValueChangeEvent e) * * @param mediaLocation the media location * @return the action string */ public String addMediaToItemGrading(String mediaLocation) { log.debug("****" + mediaLocation + " " + (new Date())); if (!mediaIsValid()) { reload = true; return "takeAssessment"; } GradingService gradingService = new GradingService(); //PublishedAssessmentService publishedService = new PublishedAssessmentService(); HashMap itemHash = getPublishedItemHash(); PersonBean person = (PersonBean) ContextUtil.lookupBean("person"); String agent = person.getId(); // 2. format of the media location is: assessmentXXX/questionXXX/agentId/myfile // 3. get the questionId (which is the PublishedItemData.itemId) //int assessmentIndex = mediaLocation.indexOf("assessment"); int questionIndex = mediaLocation.indexOf("question"); int agentIndex = mediaLocation.indexOf("/", questionIndex + 8); int myfileIndex = mediaLocation.lastIndexOf("/"); //cwen if (agentIndex < 0) { agentIndex = mediaLocation.indexOf("\\", questionIndex + 8); } //String pubAssessmentId = mediaLocation.substring(assessmentIndex + 10, questionIndex - 1); String questionId = mediaLocation.substring(questionIndex + 8, agentIndex); log.debug("***3a. addMediaToItemGrading, questionId =" + questionId); log.debug("***3b. addMediaToItemGrading, assessmentId =" + assessmentId); if (agent == null) { String agentId = mediaLocation.substring(agentIndex, myfileIndex - 1); log.debug("**** agentId=" + agentId); agent = agentId; } log.debug("***3c. addMediaToItemGrading, agent =" + agent); // 4. prepare itemGradingData and attach it to assessmentGarding PublishedItemData item = (PublishedItemData) itemHash.get(new Long(questionId)); log.debug("***4a. addMediaToItemGrading, itemText(0) =" + item.getItemTextArray().get(0)); // there is only one text in audio question PublishedItemText itemText = (PublishedItemText) item.getItemTextArraySorted().get(0); ItemGradingData itemGradingData = getItemGradingData(questionId); boolean newItemGradingData = false; if (itemGradingData == null) { newItemGradingData = true; itemGradingData = new ItemGradingData(); itemGradingData.setAssessmentGradingId(adata.getAssessmentGradingId()); itemGradingData.setPublishedItemId(item.getItemId()); itemGradingData.setPublishedItemTextId(itemText.getId()); itemGradingData.setSubmittedDate(new Date()); itemGradingData.setAgentId(agent); itemGradingData.setOverrideScore(Double.valueOf(0)); } itemGradingData.setAutoScore(Double.valueOf(0)); setAssessmentGrading(adata); // 5. save ItemGradingData alone 'cos assessmentGrading score won't be changed // we don't need to update every itemGrading in assessmentGrading gradingService.saveItemGrading(itemGradingData); //if media is uploaded, create media record and attach to itemGradingData saveMedia(agent, mediaLocation, itemGradingData, gradingService); // 8. do whatever need doing DeliveryActionListener dlistener = new DeliveryActionListener(); // false => do not reset the entire current delivery.pageContents. // we will do it ourselves and only update the question that this media // is attached to skipFlag = true; dlistener.processAction(null, false); if (newItemGradingData) attachToItemContentBean(itemGradingData, questionId); reload = true; return "takeAssessment"; // which doesn't exists to force it to reload } public void saveMedia(String agent, String mediaLocation, ItemGradingData itemGradingData, GradingService gradingService) { // 1. create a media record File media = new File(mediaLocation); byte[] mediaByte = getMediaStream(mediaLocation); String mimeType = MimeTypesLocator.getInstance().getContentType(media); boolean SAVETODB = getSaveToDb(); log.debug("**** SAVETODB=" + SAVETODB); MediaData mediaData = null; log.debug("***6a. addMediaToItemGrading, itemGradinDataId=" + itemGradingData.getItemGradingId()); // 1b. get filename String fullname = media.getName().trim(); int underscore_index = fullname.lastIndexOf("_"); int dot_index = fullname.lastIndexOf("."); String filename = fullname.substring(0, underscore_index); if (dot_index >= 0) { filename = filename + fullname.substring(dot_index); } log.debug("**** filename=" + filename); String updatedFilename = gradingService.getFileName(itemGradingData.getItemGradingId(), agent, filename); log.debug("**** updatedFilename=" + updatedFilename); if (SAVETODB) { // put the byte[] in mediaData = new MediaData(itemGradingData, mediaByte, Long.valueOf(mediaByte.length + ""), mimeType, "description", null, updatedFilename, false, false, Integer.valueOf(1), agent, new Date(), agent, new Date(), null); } else { // put the location in mediaData = new MediaData(itemGradingData, null, Long.valueOf(mediaByte.length + ""), mimeType, "description", mediaLocation, updatedFilename, false, false, Integer.valueOf(1), agent, new Date(), agent, new Date(), null); } Long mediaId = gradingService.saveMedia(mediaData); log.debug("mediaId=" + mediaId); log.debug("***6c. addMediaToItemGrading, media.itemGradinDataId=" + ((ItemGradingData) mediaData.getItemGradingData()).getItemGradingId()); log.debug("***6d. addMediaToItemGrading, mediaId=" + mediaData.getMediaId()); // 2. store mediaId in itemGradingRecord.answerText log.debug("***7. addMediaToItemGrading, adata=" + adata); itemGradingData.setAnswerText(mediaId + ""); gradingService.saveItemGrading(itemGradingData); // 3. if saveToDB, remove file from file system try { if (SAVETODB) { boolean success = media.delete(); if (!success) { log.warn("Error: media.delete() failed for mediaId =" + mediaId); } } } catch (Exception e) { log.warn(e.getMessage()); } } public boolean mediaIsValid() { boolean returnValue = true; // check if file is too big FacesContext context = FacesContext.getCurrentInstance(); ExternalContext external = context.getExternalContext(); Long fileSize = (Long) ((ServletContext) external.getContext()).getAttribute("TEMP_FILEUPLOAD_SIZE"); Long maxSize = (Long) ((ServletContext) external.getContext()).getAttribute("FILEUPLOAD_SIZE_MAX"); //log.info("**** filesize is ="+fileSize); //log.info("**** maxsize is ="+maxSize); ((ServletContext) external.getContext()).removeAttribute("TEMP_FILEUPLOAD_SIZE"); if (fileSize != null) { float fileSize_float = fileSize.floatValue() / 1024; int tmp = Math.round(fileSize_float * 10.0f); fileSize_float = (float) tmp / 10.0f; float maxSize_float = maxSize.floatValue() / 1024; int tmp0 = Math.round(maxSize_float * 10.0f); maxSize_float = (float) tmp0 / 10.0f; String err1 = (String) ContextUtil.getLocalizedString( "org.sakaiproject.tool.assessment.bundle.DeliveryMessages", "file_upload_error"); String err2 = (String) ContextUtil.getLocalizedString( "org.sakaiproject.tool.assessment.bundle.DeliveryMessages", "file_uploaded"); String err3 = (String) ContextUtil.getLocalizedString( "org.sakaiproject.tool.assessment.bundle.DeliveryMessages", "max_size_allowed"); String err4 = (String) ContextUtil .getLocalizedString("org.sakaiproject.tool.assessment.bundle.DeliveryMessages", "upload_again"); String err = err2 + fileSize_float + err3 + maxSize_float + err4; context.addMessage("file_upload_error", new FacesMessage(err1)); context.addMessage("file_upload_error", new FacesMessage(err)); returnValue = false; } return returnValue; } public boolean getNotTakeable() { return notTakeable; } public void setNotTakeable(boolean notTakeable) { this.notTakeable = notTakeable; } public boolean getPastDue() { return pastDue; } public void setPastDue(boolean pastDue) { this.pastDue = pastDue; } public long getSubTime() { return subTime; } public void setSubTime(long newSubTime) { subTime = newSubTime; } public String getSubmissionHours() { return takenHours; } public void setSubmissionHours(String newHours) { takenHours = newHours; } public String getSubmissionMinutes() { return takenMinutes; } public void setSubmissionMinutes(String newMinutes) { takenMinutes = newMinutes; } public PublishedAssessmentFacade getPublishedAssessment() { return publishedAssessment; } public void setPublishedAssessment(PublishedAssessmentFacade publishedAssessment) { this.publishedAssessment = publishedAssessment; } public java.util.Date getFeedbackDate() { return feedbackDate; } public String getFeedbackDateString() { String dateString = ""; if (feedbackDate == null) { return dateString; } try { TimeUtil tu = new TimeUtil(); dateString = tu.getDisplayDateTime(displayFormat, feedbackDate); } catch (Exception ex) { // we will leave it as an empty string log.warn("Unable to format date."); ex.printStackTrace(); } return dateString; } public void setFeedbackDate(java.util.Date feedbackDate) { this.feedbackDate = feedbackDate; } public String getFeedbackDelivery() { return feedbackDelivery; } public void setFeedbackDelivery(String feedbackDelivery) { this.feedbackDelivery = feedbackDelivery; } public String getShowScore() { return showScore; } public void setShowScore(String showScore) { this.showScore = showScore; } public boolean getHasTimeLimit() { return hasTimeLimit; } public void setHasTimeLimit(boolean hasTimeLimit) { this.hasTimeLimit = hasTimeLimit; } public String getOutcome() { return outcome; } public void setOutcome(String outcome) { this.outcome = outcome; } public String doit() { return outcome; } public boolean getAnonymousLogin() { return anonymousLogin; } public void setAnonymousLogin(boolean anonymousLogin) { this.anonymousLogin = anonymousLogin; } public ItemGradingData getItemGradingData(String publishedItemId) { ItemGradingData selected = null; if (adata != null) { Set items = adata.getItemGradingSet(); if (items != null) { Iterator iter = items.iterator(); while (iter.hasNext()) { ItemGradingData itemGradingData = (ItemGradingData) iter.next(); String itemPublishedId = itemGradingData.getPublishedItemId().toString(); if ((publishedItemId).equals(itemPublishedId)) { log.debug("*** addMediaToItemGrading, same : found it"); selected = itemGradingData; } else { log.debug("*** addMediaToItemGrading, not the same"); } } log.debug("*** addMediaToItemGrading, publishedItemId =" + publishedItemId); if (selected != null) { log.debug("*** addMediaToItemGrading, itemGradingData.publishedItemId =" + selected.getPublishedItemId().toString()); } } } return selected; } public String getContextPath() { return contextPath; } public void setContextPath(String contextPath) { this.contextPath = contextPath; } public boolean isShowStudentScore() { return showStudentScore; } public void setShowStudentScore(boolean showStudentScore) { this.showStudentScore = showStudentScore; } public boolean isShowStudentQuestionScore() { return showStudentQuestionScore; } public void setShowStudentQuestionScore(boolean param) { this.showStudentQuestionScore = param; } public boolean isTimeRunning() { return timeRunning; } public void setTimeRunning(boolean timeRunning) { this.timeRunning = timeRunning; } /** * Used for a JavaScript enable check. */ public String getJavaScriptEnabledCheck() { return this.javaScriptEnabledCheck; } /** * Used for a JavaScript enable check. */ public void setJavaScriptEnabledCheck(String javaScriptEnabledCheck) { this.javaScriptEnabledCheck = javaScriptEnabledCheck; } //cwen public void setSiteId(String siteId) { this.siteId = siteId; } public String getSiteId() { siteId = null; Placement currentPlacement = ToolManager.getCurrentPlacement(); if (currentPlacement != null) siteId = currentPlacement.getContext(); return siteId; } public String getAgentAccessString() { return deliveryAgent.getAgentInstanceString(); } public void setAgentAccessString(String agentString) { deliveryAgent.setAgentInstanceString(agentString); } public boolean getSaveToDb() { FacesContext context = FacesContext.getCurrentInstance(); ExternalContext external = context.getExternalContext(); String saveToDb = (String) ((ServletContext) external.getContext()) .getAttribute("FILEUPLOAD_SAVE_MEDIA_TO_DB"); if (("true").equals(saveToDb)) return true; else return false; } public void attachToItemContentBean(ItemGradingData itemGradingData, String questionId) { ArrayList list = new ArrayList(); list.add(itemGradingData); //find out sectionId from questionId log.debug("**** attachToItemContentBean, questionId=" + questionId); /* PublishedAssessmentService publishedService = new PublishedAssessmentService(); PublishedItemData publishedItem = publishedService. loadPublishedItem(questionId); */ PublishedItemData publishedItem = (PublishedItemData) getPublishedItemHash().get(new Long(questionId)); PublishedSectionData publishedSection = (PublishedSectionData) publishedItem.getSection(); String sectionId = publishedSection.getSectionId().toString(); SectionContentsBean partSelected = null; //get all partContents ArrayList parts = getPageContents().getPartsContents(); for (int i = 0; i < parts.size(); i++) { SectionContentsBean part = (SectionContentsBean) parts.get(i); log.debug("**** question's sectionId" + sectionId); log.debug("**** partId" + part.getSectionId()); if (sectionId.equals(part.getSectionId())) { partSelected = part; break; } } //locate the itemContentBean - the hard way, sigh... ArrayList items = new ArrayList(); if (partSelected != null) items = partSelected.getItemContents(); for (int j = 0; j < items.size(); j++) { ItemContentsBean item = (ItemContentsBean) items.get(j); if ((publishedItem.getItemId()).equals(item.getItemData().getItemId())) { // comparing itemId not object item.setItemGradingDataArray(list); break; } } } // delivery action public static final int TAKE_ASSESSMENT = 1; public static final int PREVIEW_ASSESSMENT = 2; public static final int REVIEW_ASSESSMENT = 3; public static final int GRADE_ASSESSMENT = 4; public static final int TAKE_ASSESSMENT_VIA_URL = 5; private int actionMode; private String actionString; public void setActionString(String actionString) { this.actionString = actionString; // the follwoing two values will be evaluated when reviewing assessment // based on PublishedFeedback settings setFeedback("false"); setNoFeedback("true"); if (("previewAssessment").equals(actionString)) { setActionMode(PREVIEW_ASSESSMENT); } else if (("reviewAssessment").equals(actionString)) { setActionMode(REVIEW_ASSESSMENT); } else if (("gradeAssessment").equals(actionString)) { setFeedback("true"); setNoFeedback("false"); setActionMode(GRADE_ASSESSMENT); } else if (("takeAssessment").equals(actionString)) { setActionMode(TAKE_ASSESSMENT); } else if (("takeAssessmentViaUrl").equals(actionString)) { setActionMode(TAKE_ASSESSMENT_VIA_URL); } } public String getActionString() { return actionString; } private void setActionMode(int actionMode) { this.actionMode = actionMode; } public int getActionMode() { return actionMode; } private long time = 0; public void setLastTimer(long time) { this.time = time; } public long getLastTimer() { return time; } public boolean getBeginAssessment() { return beginAssessment; } public void setBeginAssessment(boolean beginAssessment) { this.beginAssessment = beginAssessment; } public boolean timeExpired() { if (adata == null) { return false; } boolean timeExpired = false; TimedAssessmentQueue queue = TimedAssessmentQueue.getInstance(); TimedAssessmentGradingModel timedAG = (TimedAssessmentGradingModel) queue .get(adata.getAssessmentGradingId()); if (timedAG != null) { // if server already submit the assessment, this happen if JScript latency is very long // and assessment passed the time left + latency buffer // in this case, we will display the time expired message. if (timedAG.getSubmittedForGrade()) { timeExpired = true; queue.remove(timedAG); } } else { // null => not only does the assessment miss the latency buffer, it also missed the // transaction buffer timeExpired = true; } return timeExpired; } private void removeTimedAssessmentFromQueue() { if (adata == null) { return; } TimedAssessmentQueue queue = TimedAssessmentQueue.getInstance(); TimedAssessmentGradingModel timedAG = (TimedAssessmentGradingModel) queue .get(adata.getAssessmentGradingId()); if (timedAG != null) { queue.remove(timedAG); timeRunning = false; } } public void syncTimeElapsedWithServer() { if (("takeAssessment").equals(actionString) || ("takeAssessmentViaUrl").equals(actionString)) { if (adata == null) { if (log.isDebugEnabled()) { log.debug("aData is null for actionString" + actionString); } return; } TimedAssessmentQueue queue = TimedAssessmentQueue.getInstance(); TimedAssessmentGradingModel timedAG = queue.get(adata.getAssessmentGradingId()); if (timedAG != null) { int timeElapsed = Math.round((new Date().getTime() - adata.getAttemptDate().getTime()) / 1000.0f); log.debug("***setTimeElapsed=" + timeElapsed); adata.setTimeElapsed(Integer.valueOf(timeElapsed)); GradingService gradingService = new GradingService(); gradingService.saveOrUpdateAssessmentGrading(adata); setTimeElapse(adata.getTimeElapsed().toString()); } } else { // if we are in other mode, timer need not be accurate // Anyway, we don't have adata, so we haven't been using the TimerTask to keep track of it. } } public void syncTimeElapsedWithServerLinear() { if (("takeAssessment").equals(actionString) || ("takeAssessmentViaUrl").equals(actionString)) { if (adata == null) { if (log.isDebugEnabled()) { log.debug("aData is null for actionString" + actionString); } return; } TimedAssessmentQueue queue = TimedAssessmentQueue.getInstance(); TimedAssessmentGradingModel timedAG = queue.get(adata.getAssessmentGradingId()); if (timedAG != null) { int timeElapsed = Math.round((new Date().getTime() - adata.getAttemptDate().getTime()) / 1000.0f); adata.setTimeElapsed(Integer.valueOf(timeElapsed)); GradingService gradingService = new GradingService(); gradingService.saveOrUpdateAssessmentGradingOnly(adata); setTimeElapse(adata.getTimeElapsed().toString()); } } else { // if we are in other mode, timer need not be accurate // Anyway, we don't have adata, so we haven't been using the TimerTask to keep track of it. } } private String timeElapseAfterFileUpload; public String getTimeElapseAfterFileUpload() { return timeElapseAfterFileUpload; } public void setTimeElapseAfterFileUpload(String timeElapseAfterFileUpload) { this.timeElapseAfterFileUpload = timeElapseAfterFileUpload; if (timeElapseAfterFileUpload != null && !("").equals(timeElapseAfterFileUpload)) setTimeElapseAfterFileUploadDouble((Double.valueOf(timeElapseAfterFileUpload)).doubleValue()); } private double timeElapseDouble = 0; public double getTimeElapseDouble() { return timeElapseDouble; } public void setTimeElapseDouble(double timeElapseDouble) { this.timeElapseDouble = timeElapseDouble; } private double timeElapseAfterFileUploadDouble; public double getTimeElapseAfterFileUploadDouble() { return timeElapseAfterFileUploadDouble; } public void setTimeElapseAfterFileUploadDouble(double timeElapseAfterFileUploadDouble) { this.timeElapseAfterFileUploadDouble = timeElapseAfterFileUploadDouble; } private String protocol; public String getProtocol() { return protocol; } public void setProtocol(String protocol) { this.protocol = protocol; } private long timeStamp; public long getTimeStamp() { return timeStamp; } public void setTimeStamp(long timeStamp) { this.timeStamp = timeStamp; } private HashMap publishedItemHash = new HashMap(); public HashMap getPublishedItemHash() { if (this.publishedItemHash.size() == 0) { PublishedAssessmentService pubService = new PublishedAssessmentService(); this.publishedItemHash = pubService.preparePublishedItemHash(getPublishedAssessment()); } return this.publishedItemHash; } public void setPublishedItemHash(HashMap publishedItemHash) { this.publishedItemHash = publishedItemHash; } private HashMap publishedItemTextHash = new HashMap(); public HashMap getPublishedItemTextHash() { if (this.publishedItemTextHash.size() == 0) { PublishedAssessmentService pubService = new PublishedAssessmentService(); this.publishedItemTextHash = pubService.preparePublishedItemTextHash(getPublishedAssessment()); } return this.publishedItemTextHash; } public void setPublishedItemTextHash(HashMap publishedItemTextHash) { this.publishedItemTextHash = publishedItemTextHash; } private HashMap publishedAnswerHash = new HashMap(); public HashMap getPublishedAnswerHash() { if (this.publishedAnswerHash.size() == 0) { PublishedAssessmentService pubService = new PublishedAssessmentService(); this.publishedAnswerHash = pubService.preparePublishedAnswerHash(getPublishedAssessment()); } return this.publishedAnswerHash; } public void setPublishedAnswerHash(HashMap publishedAnswerHash) { this.publishedAnswerHash = publishedAnswerHash; } public boolean getIsMoreThanOneQuestion() { log.debug("getIsMoreThanOneQuestion() starts"); ArrayList partsContents = this.pageContents.getPartsContents(); if (partsContents.size() == 1) { String size = ((SectionContentsBean) partsContents.get(0)).getItemContentsSize(); log.debug("ItemContentsSize = " + size); if ("1".equals(size)) { log.debug("isMoreThanOneQuestion set to false"); isMoreThanOneQuestion = false; } } else { log.debug("isMoreThanOneQuestion set to true"); isMoreThanOneQuestion = true; } return isMoreThanOneQuestion; } private List attachmentList; public List getAttachmentList() { return attachmentList; } public void setAttachmentList(List attachmentList) { this.attachmentList = attachmentList; } private boolean hasAttachment = false; public boolean getHasAttachment() { boolean hasAttachment = false; if (attachmentList != null && attachmentList.size() > 0) { hasAttachment = true; } return hasAttachment; } public boolean getNoQuestions() { return noQuestions; } public void setNoQuestions(boolean noQuestions) { this.noQuestions = noQuestions; } public String checkBeforeProceed(boolean isSubmitForGrade, boolean isFromTimer, boolean isViaUrlLogin) { // public method, who know if publishedAssessment is set, so check // to be sure if (getPublishedAssessment() == null) { return "error"; } boolean acceptLateSubmission = AssessmentAccessControlIfc.ACCEPT_LATE_SUBMISSION .equals(publishedAssessment.getAssessmentAccessControl().getLateHandling()); if (this.actionMode == PREVIEW_ASSESSMENT) { return "safeToProceed"; } GradingService service = new GradingService(); AssessmentGradingData assessmentGrading = null; if (adata != null) { assessmentGrading = service.load(adata.getAssessmentGradingId().toString(), false); } extendedTimeService = new ExtendedTimeService(publishedAssessment); // log.debug("check 0"); if (isRemoved()) { return "isRemoved"; } log.debug("check 1"); // check 0: check for start date if (!isAvailable()) { return ("assessmentNotAvailable"); } log.debug("check 2"); // check 2: is it still available? if (!isFromTimer && isRetracted(isSubmitForGrade) && acceptLateSubmission) { return "isRetracted"; } log.debug("check 3"); // check 3: is it still available? if (isRetractedForEdit()) { return "isRetractedForEdit"; } log.debug("check 4"); // check 4: check for multiple window & browser trick boolean discrepancyInData = false; if (assessmentGrading != null && !checkDataIntegrity(assessmentGrading)) { discrepancyInData = true; } log.debug("check 5"); // check 5: if workingassessment has been submiited? // this is to prevent student submit assessment and use a 2nd window to // continue working on the submitted work. if (assessmentGrading != null && getAssessmentHasBeenSubmitted(assessmentGrading)) { return "assessmentHasBeenSubmitted"; } log.debug("check 6"); // check 6: is it need to resubmit? If yes, we don't check on submission number, dates, or time. if (isNeedResubmit() && !discrepancyInData) { return "safeToProceed"; } if (numberRetake == -1 || actualNumberRetake == -1) { GradingService gradingService = new GradingService(); numberRetake = gradingService.getNumberRetake(publishedAssessment.getPublishedAssessmentId(), AgentFacade.getAgentString()); actualNumberRetake = gradingService.getActualNumberRetake( publishedAssessment.getPublishedAssessmentId(), AgentFacade.getAgentString()); } log.debug("check 7"); // check 7: any submission attempt left? if (!getHasSubmissionLeft(numberRetake)) { return "noSubmissionLeft"; } log.debug("check 8"); // check 8: has dueDate arrived? if so, does it allow late submission? // If it is a timed assessment and "No Late Submission" and not during a Retake, always go through. Because in this case the // assessment will be auto-submitted anyway - when time is up or when current date reaches due date (if the time limited is // longer than due date,) for either case, we want to redirect to the normal "submision successful page" after submitting. if (pastDueDate()) { // If Accept Late and there is no submission yet, go through if (acceptLateSubmission) { if (totalSubmissions == 0) { log.debug("Accept Late Submission && totalSubmissions == 0"); } else { log.debug("take from bean: actualNumberRetake =" + actualNumberRetake); // Not during a Retake if (actualNumberRetake == numberRetake) { return "noLateSubmission"; } // During a Retake else if (actualNumberRetake == numberRetake - 1) { log.debug("actualNumberRetake == numberRetake - 1: through Retake"); } // Should not come to here else { log.error("Should NOT come to here - wrong actualNumberRetake or numberRetake"); } } } else { if (!isRetracted(isSubmitForGrade)) { log.debug("take from bean: actualNumberRetake =" + actualNumberRetake); // Not during a Retake if (actualNumberRetake == numberRetake) { // When taking the assessment via URL (from LoginServlet), if pass due date, throw an error if (isViaUrlLogin) { return "noLateSubmission"; } // If No Late, this is a timed assessment, and not during a Retake, go through (see above reason) else if (this.isTimedAssessment()) { log.debug("No Late Submission && timedAssessment"); } else { log.debug("noLateSubmission"); return "noLateSubmission"; } } // During a Retake else if (actualNumberRetake == numberRetake - 1) { log.debug("actualNumberRetake == numberRetake - 1: through Retake"); } // Should not come to here else { log.error("Should NOT come to here - wrong actualNumberRetake or numberRetake"); } } else { return "isRetracted"; } } } if (discrepancyInData) { return ("discrepancyInData"); } log.debug("check9"); // check 9: is timed assessment? and time has expired? if (isTimeRunning() && timeExpired() && !turnIntoTimedAssessment) { return "timeExpired"; } return "safeToProceed"; } public String checkFromViaUrlLogin() { return checkBeforeProceed(false, false, true); } public String checkBeforeProceed() { return checkBeforeProceed(false, false); } public String checkBeforeProceed(boolean isSubmitForGrade, boolean isFromTimer) { return checkBeforeProceed(isSubmitForGrade, isFromTimer, false); } private boolean getHasSubmissionLeft(int numberRetake) { boolean hasSubmissionLeft = false; int maxSubmissionsAllowed = 9999; if ((Boolean.FALSE).equals(publishedAssessment.getAssessmentAccessControl().getUnlimitedSubmissions())) { maxSubmissionsAllowed = publishedAssessment.getAssessmentAccessControl().getSubmissionsAllowed() .intValue(); if ("takeAssessmentViaUrl".equals(actionString) && !anonymousLogin && settings == null) { SettingsDeliveryBean settingsDeliveryBean = new SettingsDeliveryBean(); settingsDeliveryBean.setAssessmentAccessControl(publishedAssessment); settingsDeliveryBean.setMaxAttempts(maxSubmissionsAllowed); settings = settingsDeliveryBean; } } if (totalSubmissions < maxSubmissionsAllowed + numberRetake) { hasSubmissionLeft = true; } return hasSubmissionLeft; } private boolean isAvailable() { boolean isAvailable = true; Date currentDate = new Date(); Date startDate = new Date(); if (extendedTimeService.hasExtendedTime()) { startDate = extendedTimeService.getStartDate(); } else { startDate = publishedAssessment.getAssessmentAccessControl().getStartDate(); } if (startDate != null && startDate.after(currentDate)) { isAvailable = false; } return isAvailable; } private boolean pastDueDate() { boolean pastDue = true; Date currentDate = new Date(); Date dueDate = new Date(); if (extendedTimeService.hasExtendedTime()) { dueDate = extendedTimeService.getDueDate(); } else { dueDate = publishedAssessment.getAssessmentAccessControl().getDueDate(); } if (dueDate == null || dueDate.after(currentDate)) { pastDue = false; } return pastDue; } private boolean isRetracted(boolean isSubmitForGrade) { boolean isRetracted = true; Date currentDate = new Date(); Date retractDate = null; if (extendedTimeService.hasExtendedTime()) { retractDate = extendedTimeService.getRetractDate(); } else { retractDate = publishedAssessment.getAssessmentAccessControl().getRetractDate(); } if (retractDate == null || retractDate.after(currentDate)) { isRetracted = false; } return isRetracted; } private boolean isRemoved() { Integer status = publishedAssessment.getStatus(); if (status.equals(AssessmentBaseIfc.DEAD_STATUS)) { return true; } return false; } private boolean isRetractedForEdit() { Integer status = publishedAssessment.getStatus(); if (status.equals(AssessmentBaseIfc.RETRACT_FOR_EDIT_STATUS)) { return true; } return false; } private boolean isNeedResubmit() { if (adata == null) { return false; } Integer status = adata.getStatus(); if (status.equals(AssessmentGradingData.ASSESSMENT_UPDATED_NEED_RESUBMIT)) { return true; } return false; } private boolean checkDataIntegrity(AssessmentGradingData assessmentGrading) { // get assessmentGrading from DB, this is to avoid same assessment being // opened in the differnt browser if (assessmentGrading != null) { long DBdate = 0; if (assessmentGrading.getSubmittedDate() != null) { DBdate = assessmentGrading.getSubmittedDate().getTime(); } String browserDateString = ContextUtil.lookupParam("lastSubmittedDate1"); if (browserDateString == null) { browserDateString = ContextUtil.lookupParam("lastSubmittedDate2"); } // SAK-7106:jsf doesn't like id with same name even though there is a rendering condition there // so we have to use 2 differnt id and check it this way instead. long browserDate = 0; try { if (browserDateString != null) { browserDate = Long.parseLong(browserDateString); } else { return true; } } catch (Exception e) { log.warn(e.getMessage()); } log.debug("last modified date in DB=" + DBdate); log.debug("last modified date in browser=" + browserDate); log.debug("date is equal=" + (DBdate == browserDate)); return (DBdate == browserDate); } else return true; } private boolean getAssessmentHasBeenSubmitted(AssessmentGradingData assessmentGrading) { // get assessmentGrading from DB, this is to avoid same assessment being // opened in the differnt browser if (assessmentGrading != null) { return assessmentGrading.getForGrade().booleanValue(); } else return false; } public String getPortal() { return ServerConfigurationService.getString("portalPath"); } public String getSelectURL() { Session session = SessionManager.getCurrentSession(); String returnUrl = (String) session.getAttribute("LESSONBUILDER_RETURNURL_SAMIGO"); if (returnUrl != null) return returnUrl; StringBuilder url = new StringBuilder(ServerConfigurationService.getString("portalPath")); url.append("/site/"); PublishedAssessmentService publishedAssessmentService = new PublishedAssessmentService(); String currentSiteId = publishedAssessmentService.getPublishedAssessmentSiteId(getAssessmentId()); url.append(currentSiteId); url.append("/page/"); url.append(getCurrentPageId(currentSiteId)); return url.toString(); } public String getTimerId() { return timerId; } public void setTimerId(String timerId) { this.timerId = timerId; } private Site getCurrentSite(String id) { Site site = null; //Placement placement = ToolManager.getCurrentPlacement(); //String currentSiteId = placement.getContext(); try { site = SiteService.getSite(id); } catch (IdUnusedException e) { log.error(e.getMessage()); e.printStackTrace(); } return site; } private String getCurrentPageId(String id) { Site currentSite = getCurrentSite(id); if (currentSite == null) { return ""; } SitePage page = null; String toolId = null; try { // get page List pageList = currentSite.getPages(); for (int i = 0; i < pageList.size(); i++) { page = (SitePage) pageList.get(i); List pageToolList = page.getTools(); //toolId = ((ToolConfiguration) pageToolList.get(0)).getTool().getId(); // issue with null tool if (pageToolList.get(0) == null && ((ToolConfiguration) pageToolList.get(0)).getTool() == null) { continue; } toolId = ((ToolConfiguration) pageToolList.get(0)).getToolId(); if (toolId.equalsIgnoreCase("sakai.samigo")) { return page.getId(); } } } catch (Exception e) { log.warn(e.getMessage()); } return ""; } public Long getAssessmentGradingId() { return assessmentGradingId; } public void setFromTableOfContents(boolean fromTableOfContents) { this.fromTableOfContents = fromTableOfContents; } public boolean getFromTableOfContents() { return fromTableOfContents; } public void setAssessmentGradingId(Long assessmentGradingId) { this.assessmentGradingId = assessmentGradingId; } public String updateTimeLimit(String timeLimit) { if (numberRetake == -1 || actualNumberRetake == -1) { GradingService gradingService = new GradingService(); numberRetake = gradingService.getNumberRetake(publishedAssessment.getPublishedAssessmentId(), AgentFacade.getAgentString()); log.debug("numberRetake = " + numberRetake); actualNumberRetake = gradingService.getActualNumberRetake( publishedAssessment.getPublishedAssessmentId(), AgentFacade.getAgentString()); log.debug("actualNumberRetake =" + actualNumberRetake); } if (!("previewAssessment").equals(actionString) && actualNumberRetake >= numberRetake && beginTime != null) { timeLimit = getTimeBeforeDueRetract(timeLimit); } return timeLimit; } public String getDeadlineString() { String deadlineString = ""; if (deadline == null) { return deadlineString; } try { TimeUtil tu = new TimeUtil(); deadlineString = tu.getDisplayDateTime(dayDisplayFormat, deadline); } catch (Exception ex) { // we will leave it as an empty string log.warn("Unable to format date."); ex.printStackTrace(); } return deadlineString; } public Date getDeadline() { return deadline; } public void setDeadline() { if (this.firstTimeTaking) { boolean acceptLateSubmission = AssessmentAccessControlIfc.ACCEPT_LATE_SUBMISSION .equals(publishedAssessment.getAssessmentAccessControl().getLateHandling()); if (dueDate != null) { if (!acceptLateSubmission) { deadline = dueDate; } else { if (totalSubmissions > 0) { deadline = dueDate; } else { if (retractDate != null) { deadline = retractDate; } } } } else { if (retractDate != null) { deadline = retractDate; } } } else { if (dueDate != null) { deadline = dueDate; } else { if (retractDate != null) { deadline = retractDate; } else { deadline = null; } } } } public String getTimeBeforeDueRetract(String timeLimit) { boolean acceptLateSubmission = AssessmentAccessControlIfc.ACCEPT_LATE_SUBMISSION .equals(publishedAssessment.getAssessmentAccessControl().getLateHandling()); String finalTimeLimit = timeLimit; if (dueDate != null) { if (!acceptLateSubmission) { finalTimeLimit = getTimeBeforeDue(timeLimit); } else { if (totalSubmissions > 0) { finalTimeLimit = getTimeBeforeDue(timeLimit); } else { if (retractDate != null) { finalTimeLimit = getTimeBeforeRetract(timeLimit); useDueDate = false; } } } } else { if (retractDate != null) { finalTimeLimit = getTimeBeforeRetract(timeLimit); useDueDate = false; } } return finalTimeLimit; } private String getTimeBeforeDue(String timeLimit) { if (timeLimit != null && Integer.parseInt(timeLimit) > 0) { int timeBeforeDue = Math.round((dueDate.getTime() - beginTime.getTime()) / 1000.0f); if (timeBeforeDue < Integer.parseInt(timeLimit)) { timeLimit = String.valueOf(timeBeforeDue); } } else { int timeBeforeDue = Math.round((dueDate.getTime() - System.currentTimeMillis()) / 1000.0f); timeLimit = String.valueOf(timeBeforeDue); } return timeLimit; } private String getTimeBeforeRetract(String timeLimit) { if (timeLimit != null && Integer.parseInt(timeLimit) > 0) { int timeBeforeRetract = Math.round((retractDate.getTime() - beginTime.getTime()) / 1000.0f); if (timeBeforeRetract < Integer.parseInt(timeLimit)) { timeLimit = String.valueOf(timeBeforeRetract); } } else { int timeBeforeRetract = Math.round((retractDate.getTime() - System.currentTimeMillis()) / 1000.0f); timeLimit = String.valueOf(timeBeforeRetract); } return timeLimit; } private boolean isTimedAssessment() { if (this.getPublishedAssessment().getAssessmentAccessControl().getTimeLimit().equals(Integer.valueOf(0))) { return false; } return true; } public String cleanRadioButton() { // We get the id of the question String radioId = (String) FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap() .get("radioId"); StringBuffer redrawAnchorName = new StringBuffer("p"); String tmpAnchorName = ""; ArrayList parts = this.pageContents.getPartsContents(); for (int i = 0; i < parts.size(); i++) { SectionContentsBean sectionContentsBean = (SectionContentsBean) parts.get(i); String partSeq = sectionContentsBean.getNumber(); ArrayList items = sectionContentsBean.getItemContents(); for (int j = 0; j < items.size(); j++) { ItemContentsBean item = (ItemContentsBean) items.get(j); //Just delete the checkbox of the current question if (!item.getItemData().getItemId().toString().equals(radioId)) continue; String itemSeq = item.getItemData().getSequence().toString(); redrawAnchorName.append(partSeq); redrawAnchorName.append("q"); redrawAnchorName.append(itemSeq); if (tmpAnchorName.equals("") || tmpAnchorName.compareToIgnoreCase(redrawAnchorName.toString()) > 0) { tmpAnchorName = redrawAnchorName.toString(); } if (item.getItemData().getTypeId().longValue() == TypeIfc.MULTIPLE_CHOICE.longValue() || item.getItemData().getTypeId().longValue() == TypeIfc.MULTIPLE_CORRECT_SINGLE_SELECTION .longValue() || item.getItemData().getTypeId().longValue() == TypeIfc.MULTIPLE_CHOICE_SURVEY.longValue() || item.getItemData().getTypeId().longValue() == TypeIfc.MATRIX_CHOICES_SURVEY .longValue()) { item.setUnanswered(true); if (item.getItemData().getTypeId().longValue() == TypeIfc.MATRIX_CHOICES_SURVEY.longValue()) { for (int k = 0; k < item.getMatrixArray().size(); k++) { MatrixSurveyBean selection = (MatrixSurveyBean) item.getMatrixArray().get(k); selection.setResponseFromCleanRadioButton(); } } else { for (int k = 0; k < item.getSelectionArray().size(); k++) { SelectionBean selection = (SelectionBean) item.getSelectionArray().get(k); //selection.setResponse(false); selection.setResponseFromCleanRadioButton(); } } ArrayList itemGradingData = new ArrayList(); Iterator iter = item.getItemGradingDataArray().iterator(); while (iter.hasNext()) { ItemGradingData itemgrading = (ItemGradingData) iter.next(); if (itemgrading.getItemGradingId() != null && itemgrading.getItemGradingId().intValue() > 0) { itemGradingData.add(itemgrading); itemgrading.setPublishedAnswerId(null); } } item.setItemGradingDataArray(itemGradingData); } if (item.getItemData().getTypeId().longValue() == TypeIfc.TRUE_FALSE.longValue()) { item.setResponseId(null); Iterator iter = item.getItemGradingDataArray().iterator(); if (iter.hasNext()) { ItemGradingData data = (ItemGradingData) iter.next(); data.setPublishedAnswerId(null); } } item.setReview(false); item.setRationale(""); } } syncTimeElapsedWithServer(); // Set the anchor setRedrawAnchorName(tmpAnchorName.toString()); return "takeAssessment"; } /** * * * @return */ public boolean getDisplayMardForReview() { return this.displayMardForReview; } /** * * * @param reviewMarked */ public void setDisplayMardForReview(boolean displayMardForReview) { this.displayMardForReview = displayMardForReview; } public HashMap getItemContentsMap() { return this.itemContentsMap; } public void setItemContentsMap(HashMap itemContentsMap) { this.itemContentsMap = itemContentsMap; } public String getAutoSaveRepeatMilliseconds() { String s = ServerConfigurationService.getString("samigo.autoSave.repeat.milliseconds"); try { Integer.parseInt(s); } catch (NumberFormatException ex) { s = "-1"; } log.debug("auto save every " + s + " milliseconds"); return s; } public void setFileUploadSizeMax(int fileUploadSizeMax) { this.fileUploadSizeMax = fileUploadSizeMax; } public int getFileUploadSizeMax() { return fileUploadSizeMax; } public boolean getStudentRichText() { String studentRichText = ServerConfigurationService.getString("samigo.studentRichText", "true"); return Boolean.parseBoolean(studentRichText); } public void setDisplayFormat() { display_dateFormat = ContextUtil.getLocalizedString( "org.sakaiproject.tool.assessment.bundle.GeneralMessages", "output_date_no_sec"); displayFormat = new SimpleDateFormat(display_dateFormat, new ResourceLoader().getLocale()); } public Integer getScoringType() { return scoringType; } public void setScoringType(Integer scoringType) { this.scoringType = scoringType; } public String getSecureDeliveryHTMLFragment() { return this.secureDeliveryHTMLFragment; } public void setSecureDeliveryHTMLFragment(String secureDeliveryHTMLFragment) { this.secureDeliveryHTMLFragment = secureDeliveryHTMLFragment; } public boolean isBlockDelivery() { return blockDelivery; } public void setBlockDelivery(boolean blockDelivery) { this.blockDelivery = blockDelivery; } public boolean getIsFromPrint() { return isFromPrint; } public void setIsFromPrint(boolean isFromPrint) { this.isFromPrint = isFromPrint; } public boolean getIsAnyInvalidFinInput() { return isAnyInvalidFinInput; } public void setIsAnyInvalidFinInput(boolean isAnyInvalidFinInput) { this.isAnyInvalidFinInput = isAnyInvalidFinInput; } public String getRedrawAnchorName() { return redrawAnchorName; } public void setRedrawAnchorName(String redrawAnchorName) { this.redrawAnchorName = redrawAnchorName; } public Boolean getHonorPledge() { return honorPledge; } public void setHonorPledge(Boolean honorPledge) { this.honorPledge = honorPledge; } public String getRecURL() { if (RECPATH == null || RECPATH.trim().equals("")) { return ""; } String recURL = ACCESSBASE + RECPATH; return recURL; } /** * Return the time limit as a String * * @param delivery * @param publishedAssessment * @param fromBeginAssessment * @return */ public int evaluateTimeLimit(PublishedAssessmentFacade pubAssessment, Boolean fromBeginAssessment, int extTimeVal) { publishedAssessment = pubAssessment; // synchronize the passed in values Integer timeLimit = 0; Integer originalTimeLimit = publishedAssessment.getAssessmentAccessControl().getTimeLimit(); int extTimeAdjust = 0; // Calcuate the adjustment due to extended time if necessary if (extTimeVal > 0) { extTimeAdjust = extTimeVal - originalTimeLimit; // adjustment to add // to time remaining } if (fromBeginAssessment) { timeLimit = Integer.parseInt(this.updateTimeLimit(originalTimeLimit.toString())) + extTimeAdjust; } else { if (this.getTimeLimit() != null) { timeLimit = Integer.parseInt(this.getTimeLimit()); } } this.setTimeLimit(timeLimit.toString()); return timeLimit; } public void setNumberRetake(int numberRetake) { this.numberRetake = numberRetake; } public int getNumberRetake() { return numberRetake; } public void setActualNumberRetake(int actualNumberRetake) { this.actualNumberRetake = actualNumberRetake; } public int getActualNumberRetake() { return actualNumberRetake; } public boolean getShowTimeWarning() { return showTimeWarning; } public void setShowTimeWarning(boolean showTimeWarning) { this.showTimeWarning = showTimeWarning; } public boolean getHasShowTimeWarning() { return hasShowTimeWarning; } public void setHasShowTimeWarning(boolean hasShowTimeWarning) { this.hasShowTimeWarning = hasShowTimeWarning; } public boolean getTurnIntoTimedAssessment() { return turnIntoTimedAssessment; } public void setTurnIntoTimedAssessment(boolean turnIntoTimedAssessment) { this.turnIntoTimedAssessment = turnIntoTimedAssessment; } public boolean getUseDueDate() { return useDueDate; } public void setUseDueDate(boolean useDueDate) { this.useDueDate = useDueDate; } public boolean getsubmitFromTimeoutPopup() { return submitFromTimeoutPopup; } public void setSubmitFromTimeoutPopup(boolean submitFromTimeoutPopup) { this.submitFromTimeoutPopup = submitFromTimeoutPopup; } public void setSkipFlag(boolean skipFlag) { this.skipFlag = skipFlag; } public boolean getSkipFlag() { return skipFlag; } public void setFirstTimeTaking(boolean firstTimeTaking) { this.firstTimeTaking = firstTimeTaking; } public boolean getFirstTimeTaking() { return firstTimeTaking; } //SAM-2517 public boolean getIsMathJaxEnabled() { PublishedAssessmentService publishedAssessmentService = new PublishedAssessmentService(); String siteId = publishedAssessmentService.getPublishedAssessmentOwner(Long.parseLong(getAssessmentId())); String strMathJaxEnabled = getCurrentSite(siteId).getProperties().getProperty(MATHJAX_ENABLED); return StringUtils.contains(strMathJaxEnabled, "sakai.samigo"); } public String getMathJaxHeader() { StringBuilder headMJ = new StringBuilder(); headMJ.append( "<script type=\"text/x-mathjax-config\">\nMathJax.Hub.Config({\ntex2jax: { inlineMath: [['$$','$$'],['\\\\(','\\\\)']] }, TeX: { equationNumbers: { autoNumber: 'AMS' } }\n});\n</script>\n"); headMJ.append("<script src=\"").append(MATHJAX_SRC_PATH) .append("\" language=\"JavaScript\" type=\"text/javascript\"></script>\n"); return headMJ.toString(); } public String getQuestionProgressUnansweredPath() { return questionProgressUnansweredPath; } public String getQuestionProgressAnsweredPath() { return questionProgressAnsweredPath; } public String getQuestionProgressMardPath() { return questionProgressMardPath; } }