diff --git a/samigo/samigo-app/pom.xml b/samigo/samigo-app/pom.xml index 47da75f1b1b7..b851f3dac573 100644 --- a/samigo/samigo-app/pom.xml +++ b/samigo/samigo-app/pom.xml @@ -155,6 +155,12 @@ jsf-api 1.1.01 --> + + org.scilab.forge + jlatexmath + 1.0.7 + + OKI OkiOSID @@ -261,6 +267,11 @@ jakarta.servlet.jsp.jstl-api ${sakai.jakarta.jstl-api.version} + + org.json + json + ${sakai.org.json.version} + diff --git a/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/AuthorMessages.properties b/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/AuthorMessages.properties index e14c8c583607..8dcc1a797444 100755 --- a/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/AuthorMessages.properties +++ b/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/AuthorMessages.properties @@ -879,3 +879,38 @@ fixed_and_random_draw_msg_no_date=The questions for this part are generated fixe edit_published_assessment_warn_edit_pool_fixed_questions=Your changes will affect this assessment only, not the question pool(s) from which these questions were fixed or drawn. To edit the corresponding fixed questions in the question pool named {0} and edit the corresponding questions in the question pool named {1}, use the Question Pools link at the top of this page. notfixedquestionselected_error=You must select at least one fixed question. notenough_error=There is not enough questions. The sum of fixed questions and random draw questions must not exceed the sum of the total questions in the selected question pool. + +# S2U-9 +type.1=Multiple Choice +type.2=Multiple Choice +type.3=Multiple Choice +type.4=True / False +type.5=Short Answer +type.6=File Upload +type.7=Audio Recording +type.8=Fill in the Blank +type.9=Matching +type.11=Fill in Numeric +type.12=Multiple Choice +type.13=Survey +type.14=Matching +type.15=Calculated Question +type.16=Hot Spot + +score_format = {0} of {1} ({2}%) +short_summary.title = Question title +short_summary.type = Type +short_summary.answered = Answered +short_summary.answered.yes = Yes +short_summary.answered.no = No +short_summary.score = Score +short_summary.part_title = Part {0} - {1} - {2}/{3} - {4}/{5} points + +comments_for_student=Comments for Student: +comment_for_student=Comment for Student: + +current_question=Question {0} of {1} +no_answer.text = Unanswered +attachments.name = - Name: +audio.record = There is an audio recording +audio.no_record = There is no audio recording diff --git a/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/AuthorMessages_ca.properties b/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/AuthorMessages_ca.properties index 76078c8a880c..3e1802619b65 100644 --- a/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/AuthorMessages_ca.properties +++ b/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/AuthorMessages_ca.properties @@ -874,3 +874,38 @@ fixed_and random_draw_msg_no_date=Las preguntas de esta parte est\u00e1n formada edit_published_assessment_warn_edit_pool_fixed_questions=Your changes will affect this assessment only, not the question pool(s) from which these questions were fixed or drawn. To edit the corresponding fixed questions in the question pool named {0} and edit the corresponding questions in the question pool named {1}, use the Question Pools link at the top of this page. notfixedquestionselected_error=Debes seleccionar al menos una pregunta fija. notenough_error=There is not enough questions. The sum of fixed questions and random draw questions must not exceed the sum of the total questions in the selected question pool. + +# S2U-9 +type.1=Selecci\u00f3 M\u00faltiple +type.2=Selecci\u00f3 M\u00faltiple +type.3=Selecci\u00f3 M\u00faltiple +type.4=Veritat / Fals +type.5=Resposta curta +type.6=C\u00e0rrega de fitxer +type.7=Gravaci\u00f3 Audio +type.8=Omple els espais +type.9=Relaciona +type.11=Resposta num\u00e8rica +type.12=Selecci\u00f3 M\u00faltiple +type.13=Enquesta +type.14=Relaciona +type.15=Pregunta calculada +type.16=Imatge interactiva + +score_format = {0} de {1} ({2}%) +short_summary.title = Text de la pregunta +short_summary.type = Tipus +short_summary.answered = Respon +short_summary.answered.yes = S\u00ed +short_summary.answered.no = No +short_summary.score = Puntuaci\u00f3 +short_summary.part_title = Part {0} - {1} - {2}/{3} - {4}/{5} punts + +comments_for_student=Comentaris per a l\u2019estudiant: +comment_for_student=Comentari per a l\u2019estudiant: + +current_question=Pregunta {0} de {1} +no_answer.text = Sense Resposta +attachments.name = - Nom: +audio.record = Hi ha un Gravaci\u00f3 Audio +audio.no_record = No hi ha Gravaci\u00f3 Audio diff --git a/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/AuthorMessages_es.properties b/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/AuthorMessages_es.properties index 8f021f644f10..89814bf78185 100644 --- a/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/AuthorMessages_es.properties +++ b/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/AuthorMessages_es.properties @@ -873,3 +873,38 @@ fixed_and_random_draw_msg_no_date=Las preguntas de esta parte est\u00e1n formada edit_published_assessment_warn_edit_pool_fixed_questions=Los cambios afectar\u00e1n solo a este examen, no a la bater\u00eda/(s) de preguntas de las que las preguntas fijas o aleatorias fueron extra\u00eddas. Para editar las correspondientes preguntas fijadas debes hacerlo en la bater\u00eda denominada {0} y para editar las correspondientes preguntas aleatorias hazlo desde la bater\u00eda denominada {1}. Utilice el enlace Bater\u00eda de Preguntas en la parte superior de esta p\u00e1gina. notfixedquestionselected_error=Debes seleccionar al menos una pregunta fija. notenough_error=No hay suficientes preguntas. La suma de preguntas fijas y preguntas aleatorias no debe ser superior a la suma total de las preguntas de la bater\u00eda seleccionada. + +# S2U-9 +type.1=Opci\u00f3n Multiple +type.2=Opci\u00f3n Multiple +type.3=Opci\u00f3n Multiple +type.4=Verdadero / Falso +type.5=Respuesta corta +type.6=Subir archivos +type.7=Grabaci\u00f3n de audio +type.8=Completar los espacios en blanco +type.9=Relacionar +type.11=Respuesta num\u00e9rica +type.12=Opci\u00f3n Multiple +type.13=Encuesta +type.14=Relacionar +type.15=Pregunta calculada +type.16=Imagen interactiva + +score_format = {0} de {1} ({2}%) +short_summary.title = T\u00edtulo de pregunta +short_summary.type = Tipo +short_summary.answered = Respondida +short_summary.answered.yes = S\u00ed +short_summary.answered.no = No +short_summary.score = Puntuaci\u00f3n +short_summary.part_title = Parte {0} - {1} - {2}/{3} - {4}/{5} puntos + +comments_for_student=Comentarios para el estudiante: +comment_for_student=Comentario para el estudiante: + +current_question=Pregunta {0} de {1} +no_answer.text = Sin Respuesta +attachments.name = - Nombre: +audio.record = Hay una grabaci\u00f3n de audio +audio.no_record = No hay ninguna grabaci\u00f3n de audio diff --git a/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/AuthorMessages_eu.properties b/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/AuthorMessages_eu.properties index efabfc7ecd57..3d823d74107a 100644 --- a/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/AuthorMessages_eu.properties +++ b/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/AuthorMessages_eu.properties @@ -873,3 +873,38 @@ fixed_and_random_draw_msg_no_date=Parte honetako galderak bi modutan jaso dira: edit_published_assessment_warn_edit_pool_fixed_questions=Aldaketak eragina izango dute azterketa honetan soilik, ez erabilitako biltzaileetan. "{0}" izeneko biltzailean dauden galdera finkoak editatzeko sakatu goiko esteka, Galderak, orrialde honetan goian dagoena. Gauza bera egin editatzeko "{1}" izeneko biltzailean dauden galderak. notfixedquestionselected_error=Gutxienez galdera finko bat hautatu behar duzu. notenough_error=Ez dago galdera nahikorik biltzailean. Galdera finkoen eta ausaz ateratako galderen baturak ez du gainditu behar erabilitako biltzailean daudenak. + +# S2U-9 +type.1=Hainbat aukera dituztenak erantzuteko +type.2=Hainbat aukera dituztenak erantzuteko +type.3=Hainbat aukera dituztenak erantzuteko +type.4=Egiazkoa / Faltsua +type.5=Erantzun laburra +type.6=Kargatu fitxategia +type.7=audio grabazioak +type.8=Zuriunetan idazteko direnak +type.9=Parekatzekoak +type.11=Zenbakizko erantzunak +type.12=Hainbat aukera dituztenak erantzuteko +type.13=Inkestak +type.14=Parekatzekoak +type.15=Galdera kalkulatua +type.16=Irudi interaktiboa + +score_format = {1}etik {0} ({2}%) +short_summary.title = Galderaren izenburua +short_summary.type = Mota +short_summary.answered = Erantzun +short_summary.answered.yes = Bai +short_summary.answered.no = Ez +short_summary.score = Puntuazioa +short_summary.part_title = {0} zatia - {1} - {2}/{3} - {4}/{5} puntu + +comments_for_student=Ikasleentzako iruzkinak: +comment_for_student=Ikasleentzako iruzkina: + +current_question={1}eko {0}. galdera +no_answer.text = Erantzun Gabe +attachments.name = - Izena: +audio.record = Audio grabaketa bat dago +audio.no_record = Ez dago audio grabaketarik diff --git a/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/EvaluationMessages.properties b/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/EvaluationMessages.properties index 7ffb36d461c1..977b6eec1ee0 100644 --- a/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/EvaluationMessages.properties +++ b/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/EvaluationMessages.properties @@ -397,3 +397,6 @@ unsavedchangesrubric=There are unsaved criterion changes. You should confirm or # S2U-19 fixed_random_draw_info=(fixed draw, {0} questions and random draw, {1} questions from pool of {2}): + +# S2U-9 +print_report=Print report diff --git a/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/EvaluationMessages_ca.properties b/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/EvaluationMessages_ca.properties index ef2b9b2b6361..76c1bcb197a6 100644 --- a/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/EvaluationMessages_ca.properties +++ b/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/EvaluationMessages_ca.properties @@ -394,3 +394,6 @@ unsavedchangesrubric=Hi ha canvis sense guardar en els criteris. Ha de confirmar # S2U-19 fixed_random_draw_info=(elecci\u00f3 fixa, {0} preguntes i selecci\u00f3 aleat\u00f2ria, {1} preguntes d'una bateria de {2}): + +# S2U-9 +print_report=Imprimir reporti diff --git a/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/EvaluationMessages_es.properties b/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/EvaluationMessages_es.properties index 43174efeec05..f1d0bcc82a1e 100644 --- a/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/EvaluationMessages_es.properties +++ b/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/EvaluationMessages_es.properties @@ -395,3 +395,6 @@ unsavedchangesrubric=Hay cambios sin guardar en los criterios. Debe confirmar o # S2U-19 fixed_random_draw_info=(modo fijo, {0} preguntas, modo aleatorio, {1} de {2} preguntas de la bater\u00eda)\: + +# S2U-9 +print_report=Imprimir reporte diff --git a/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/EvaluationMessages_eu.properties b/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/EvaluationMessages_eu.properties index 18ec98bcf1a3..aac7f7d9f555 100644 --- a/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/EvaluationMessages_eu.properties +++ b/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/bundle/EvaluationMessages_eu.properties @@ -394,3 +394,6 @@ unsavedchangesrubric=Gorde gabeko aldaketak daude irizpideetan. Berretsi edo utz # S2U-19 fixed_random_draw_info=(finkoak {0} galdera dira, eta ausazkoak {1} galdera dira, biltzaile honetatik hartuta: {2}): + +# S2U-9 +print_report=Inprimatu txostena diff --git a/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/listener/evaluation/ExportAction.java b/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/listener/evaluation/ExportAction.java new file mode 100644 index 000000000000..997a8826711e --- /dev/null +++ b/samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/listener/evaluation/ExportAction.java @@ -0,0 +1,1006 @@ +/********************************************************************************** + * $URL$ + * $Id$ + *********************************************************************************** + * + * Copyright (c) 2023 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.listener.evaluation; + +import com.lowagie.text.Chunk; +import com.lowagie.text.Document; +import com.lowagie.text.Element; +import com.lowagie.text.Font; +import com.lowagie.text.Image; +import com.lowagie.text.PageSize; +import com.lowagie.text.Paragraph; +import com.lowagie.text.Phrase; +import com.lowagie.text.pdf.BaseFont; +import com.lowagie.text.pdf.PdfContentByte; +import com.lowagie.text.pdf.PdfGState; +import com.lowagie.text.pdf.PdfPCell; +import com.lowagie.text.pdf.PdfPCellEvent; +import com.lowagie.text.pdf.PdfPTable; +import com.lowagie.text.pdf.PdfWriter; +import com.lowagie.text.Rectangle; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; +import java.text.DecimalFormat; +import javax.faces.event.ActionEvent; +import javax.faces.event.ActionListener; +import javax.faces.context.FacesContext; +import javax.faces.model.SelectItem; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; + +import lombok.extern.slf4j.Slf4j; +import lombok.Setter; +import lombok.Getter; + +import org.apache.commons.lang3.StringUtils; +import org.json.JSONObject; +import org.jsoup.Jsoup; +import org.jsoup.select.Elements; +import org.scilab.forge.jlatexmath.TeXFormula; + +import org.sakaiproject.component.cover.ServerConfigurationService; +import org.sakaiproject.content.api.ContentHostingService; +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.shared.TypeIfc; +import org.sakaiproject.tool.assessment.ui.bean.delivery.DeliveryBean; +import org.sakaiproject.tool.assessment.ui.bean.delivery.ImageMapQuestionBean; +import org.sakaiproject.tool.assessment.ui.bean.delivery.ItemContentsBean; +import org.sakaiproject.tool.assessment.ui.bean.delivery.MatchingBean; +import org.sakaiproject.tool.assessment.ui.bean.delivery.MatrixSurveyBean; +import org.sakaiproject.tool.assessment.ui.bean.delivery.FibBean; +import org.sakaiproject.tool.assessment.ui.bean.delivery.FinBean; +import org.sakaiproject.tool.assessment.ui.bean.delivery.SectionContentsBean; +import org.sakaiproject.tool.assessment.ui.bean.delivery.SelectionBean; +import org.sakaiproject.tool.assessment.ui.bean.evaluation.StudentScoresBean; +import org.sakaiproject.tool.assessment.ui.listener.util.ContextUtil; +import org.sakaiproject.util.ResourceLoader; + +@Slf4j +public class ExportAction implements ActionListener { + + private ContentHostingService contentHostingService = org.sakaiproject.content.cover.ContentHostingService.getInstance(); + private static final ResourceLoader rb = new ResourceLoader("org.sakaiproject.tool.assessment.bundle.AuthorMessages"); + private Color gray = new Color(221, 219, 219); + private Color grayLight = new Color(231, 228, 228); + private Color green = new Color(9, 215, 71); + private Font boldFont = new Font(Font.TIMES_ROMAN, 13, Font.BOLD); + private boolean isTable = false; + private String LATEX_SEPARATOR_DOLLAR = "$$"; + private String[] LATEX_SEPARATOR_START = {"\\(", "\\["}; + private String[] LATEX_SEPARATOR_FINAL = {"\\)", "\\]"}; + + /** + * Standard process action method. + * @param ae ActionEvent + */ + public void processAction(ActionEvent ae) { + FacesContext faces = FacesContext.getCurrentInstance(); + HttpServletResponse response = (HttpServletResponse) faces.getExternalContext().getResponse(); + DeliveryBean deliveryBean = (DeliveryBean) ContextUtil.lookupBean("delivery"); + StudentScoresBean studentScoreBean = (StudentScoresBean) ContextUtil.lookupBean("studentScores"); + + Document document = new Document(PageSize.A4, 20, 20, 20, 20); + try { + ServletOutputStream outputStream = response.getOutputStream(); + PdfWriter docWriter = PdfWriter.getInstance(document, outputStream); + document.open(); + response.setContentType("application/pdf"); + response.setHeader("Content-Disposition", "attachment; filename=Report_" + studentScoreBean.getFirstName() + "_" + deliveryBean.getAssessmentTitle() + ".pdf"); + Paragraph studentNameParagraph = new Paragraph(studentScoreBean.getStudentName(), new Font(Font.TIMES_ROMAN, 22, Font.BOLD)); + document.add(studentNameParagraph); + if (studentScoreBean.getEmail() != null && !StringUtils.equals(studentScoreBean.getEmail(), "")) { + Paragraph studentEmailParagraph = new Paragraph("(" + studentScoreBean.getEmail() + ")", new Font(Font.TIMES_ROMAN, 8, Font.BOLD, Color.GRAY)); + document.add(studentEmailParagraph); + } + + float[] pdfTableWidth = {2f, 1f}; + PdfPTable shortSummaryTable = new PdfPTable(pdfTableWidth); + this.addCellToTable(shortSummaryTable, deliveryBean.getAssessmentTitle(), 0, 0); + double currentScore = deliveryBean.getTableOfContents().getCurrentScore(); + double maxScore = deliveryBean.getTableOfContents().getMaxScore(); + DecimalFormat twoDecimalsFormat = new DecimalFormat("0.00"); + String scorePercentageString = (maxScore == 0) ? "0" : twoDecimalsFormat.format((currentScore / maxScore) * 100); + this.addCellToTable(shortSummaryTable, rb.getFormattedMessage("score_format", new String[] { twoDecimalsFormat.format(currentScore), twoDecimalsFormat.format(maxScore), scorePercentageString }), 0, 0); + document.add(shortSummaryTable); + document.add(new Paragraph(Chunk.NEWLINE)); + + int index = 0; + List deliveryParts = deliveryBean.getPageContents().getPartsContents(); + for (SectionContentsBean deliveryPart : deliveryParts) { + List items = deliveryPart.getItemContents(); + + Font blueBoldFont = new Font(Font.TIMES_ROMAN, 16, Font.BOLD); + blueBoldFont.setColor(new Color(15, 76, 114)); + String partNumber = String.valueOf(deliveryPart.getNumber()); + String partTitle = deliveryPart.getText(); + String answeredQuestions = String.valueOf((deliveryPart.getQuestions() - deliveryPart.getUnansweredQuestions())); + String questionsNumber = String.valueOf(deliveryPart.getQuestions()); + String partScore = twoDecimalsFormat.format(deliveryPart.getPoints()); + String partMaxScore = twoDecimalsFormat.format(deliveryPart.getMaxPoints()); + document.add(new Paragraph(rb.getFormattedMessage("short_summary.part_title", new String[]{partNumber, partTitle, answeredQuestions, questionsNumber, partScore, partMaxScore}), blueBoldFont)); + + document.add(new Paragraph("\n")); + + shortSummaryTable = new PdfPTable(new float[]{2f, 0.5f, 0.7f, 0.7f}); + shortSummaryTable.setWidthPercentage(95f); + this.addCellToTable(shortSummaryTable, rb.getString("short_summary.title"), 1, 0); + this.addCellToTable(shortSummaryTable, rb.getString("short_summary.type"), 1, 0); + this.addCellToTable(shortSummaryTable, rb.getString("short_summary.answered"), 1, 0); + this.addCellToTable(shortSummaryTable, rb.getString("short_summary.score"), 1, 0); + for (ItemContentsBean item : items) { + int tableColor = (index % 2 == 0)? 1 : 2; + PdfPCell questionTextCell = new PdfPCell(this.getQuestionTitle(++index + ". " + item.getText(), false)); + questionTextCell.setMinimumHeight(25f); + questionTextCell.setPadding(5f); + questionTextCell.setBorderWidth(0); + questionTextCell.setBackgroundColor((tableColor == 1)? gray : grayLight); + shortSummaryTable.addCell(questionTextCell); + this.addCellToTable(shortSummaryTable, rb.getString("type." + item.getItemData().getTypeId()), 2, tableColor); + this.addCellToTable(shortSummaryTable, (!item.isUnanswered() ? rb.getString("short_summary.answered.yes") : rb.getString("short_summary.answered.no")), 2, tableColor); + this.addCellToTable(shortSummaryTable, (twoDecimalsFormat.format(item.getPoints()) + "/" + twoDecimalsFormat.format(item.getMaxPoints())), 2, tableColor); + } + document.add(shortSummaryTable); + document.add(new Paragraph("\n")); + } + + String comments = studentScoreBean.getComments(); + if (StringUtils.isNotEmpty(comments)) { + document.add(new Paragraph(rb.getString("comments_for_student"), boldFont)); + document.add(new Paragraph(Chunk.NEWLINE)); + PdfPTable commentsTable = new PdfPTable(1); + commentsTable.setHorizontalAlignment(PdfPTable.ALIGN_LEFT); + this.addCellToTable(commentsTable, comments, 3, 0); + document.add(commentsTable); + } + document.newPage(); + + int questionsCuantity = index; + int itemsIndex = 0; + for (SectionContentsBean deliveryPart : deliveryParts) { + List items = deliveryPart.getItemContents(); + for (ItemContentsBean item : items) { + Long questionType = item.getItemData().getTypeId(); + PdfPTable questionTable = new PdfPTable(2); + questionTable.setWidthPercentage(50f); + questionTable.setHorizontalAlignment(PdfPTable.ALIGN_LEFT); + this.addCellToTable(questionTable, ( rb.getFormattedMessage("current_question", new String[]{String.valueOf(++itemsIndex), String.valueOf(questionsCuantity)}) ), 3, 1); + this.addCellToTable(questionTable, (twoDecimalsFormat.format(item.getPoints()) + "/" + twoDecimalsFormat.format(item.getMaxPoints())), 3, 0); + document.add(new Paragraph(Chunk.NEWLINE)); + document.add(questionTable); + if (questionType == TypeIfc.FILL_IN_NUMERIC || questionType == TypeIfc.CALCULATED_QUESTION || questionType == TypeIfc.FILL_IN_BLANK) { + this.processFillInQuestion(document, (questionType != TypeIfc.FILL_IN_BLANK)? item.getFinArray() : item.getFibArray(), (questionType != TypeIfc.FILL_IN_BLANK)); + } else { + document.add(this.getQuestionTitle(item.getText(), true)); + } + if (questionType == TypeIfc.ESSAY_QUESTION) { + PdfPTable responseTable = new PdfPTable(1); + responseTable.setWidthPercentage(95f); + this.addCellToTable(responseTable, (item.getResponseText() != null) ? this.cleanText(item.getResponseText()) : item.getResponseText(), 4, 0); + document.add(responseTable); + } + if (questionType == TypeIfc.FILE_UPLOAD) { + if (item.getMediaArray().size() > 0) { + document.add(new Paragraph(rb.getString("attachments") + ":")); + for (MediaData mediaData : item.getMediaArray()) { + document.add(new Paragraph(rb.getString("attachments.name") + mediaData.getFilename())); + } + + } else { + document.add(new Paragraph(rb.getString("no_attachments"))); + } + + } else if (questionType == TypeIfc.AUDIO_RECORDING) { + if (item.getMediaArray().size() > 0) { + document.add(new Paragraph(rb.getString("audio.record"))); + } else { + document.add(new Paragraph(rb.getString("audio.no_record"))); + } + } + List matrixArray = item.getMatrixArray(); + List columnsIndex = item.getColumnIndexList(); + String[] columns = item.getColumnArray(); + + if (columns != null && columnsIndex != null && matrixArray != null) { + + PdfPTable matrixTable = new PdfPTable(columnsIndex.size()+1); + matrixTable.setWidthPercentage(100f); + this.addCellToTable(matrixTable, "", 4, 0); + + for (String column : columns) { + this.addCellToTable(matrixTable, column, 5, 0); + } + for (Object matrix : matrixArray) { + if (questionType == TypeIfc.MATRIX_CHOICES_SURVEY) { + this.addCellToTable(matrixTable, (((MatrixSurveyBean) matrix).getItemText().getText()), 6, 0); + for (String answer : ((MatrixSurveyBean) matrix).getAnswerSid()) { + PdfPCell circleCell = new PdfPCell(new Paragraph(" ")); + circleCell.setBorderWidth(0); + circleCell.setBorderWidthTop(1); + circleCell.setMinimumHeight(20f); + circleCell.setCellEvent(new CircleCellEvent(StringUtils.equals(answer, ((MatrixSurveyBean) matrix).getResponseId()), true)); + matrixTable.addCell(circleCell); + } + } + } + document.add(matrixTable); + } + String imageSrc = ServerConfigurationService.getServerUrl() + item.getImageSrc(); + if (questionType == TypeIfc.IMAGEMAP_QUESTION && imageSrc.length() > 0) { + document.add(new Paragraph(Chunk.NEWLINE)); + Image image = Image.getInstance(imageSrc); + + PdfPTable tableImage = new PdfPTable(1); + tableImage.setWidthPercentage(100f); + PdfPCell cellImage = new PdfPCell(); + cellImage.setBorderWidth(0); + cellImage.setPadding(0); + cellImage.addElement(image); + ArrayList answerRectangles = new ArrayList(); + for (Object answer : item.getAnswers()) { + JSONObject jsonObject = new JSONObject((String) answer); + answerRectangles.add(new Rectangle(jsonObject.getFloat("x1"), jsonObject.getFloat("y1"), jsonObject.getFloat("x2"), jsonObject.getFloat("y2"))); + } + + List itemsGrading = item.getItemGradingDataArray(); + ArrayList answerCircles = new ArrayList(); + for (ItemGradingData itemGrading : itemsGrading) { + if (itemGrading.getAnswerText() != null && !StringUtils.equals(itemGrading.getAnswerText(), "")) { + JSONObject jsonObject = new JSONObject(itemGrading.getAnswerText()); + boolean xDefined = !StringUtils.equals(jsonObject.optString("x"), "undefined"); + boolean yDefined = !StringUtils.equals(jsonObject.optString("y"), "undefined"); + float x = (xDefined)? jsonObject.getFloat("x") : 0f; + float y = (yDefined)? jsonObject.getFloat("y") : 0f; + if (xDefined && yDefined) { + answerCircles.add(new Circle(x, y, itemGrading.getPublishedItemTextId().intValue())); + } else { + answerCircles.add(new Circle(x, y, itemGrading.getPublishedItemTextId().intValue())); + } + } + } + cellImage.setCellEvent(new ImageMapQuestionCellEvent(answerCircles, answerRectangles, image.getWidth(), image.getHeight())); + tableImage.addCell(cellImage); + document.add(tableImage); + } + + for (Object answer : item.getAnswers()) { + if (questionType == TypeIfc.MULTIPLE_CHOICE || questionType == TypeIfc.MULTIPLE_CORRECT_SINGLE_SELECTION + || questionType == TypeIfc.MULTIPLE_CHOICE_SURVEY || questionType == TypeIfc.MULTIPLE_CORRECT) + { + SelectionBean selectionBean = (SelectionBean) answer; + PdfPTable multipleTable = new PdfPTable(1); + multipleTable.setWidthPercentage(100f); + + PdfPCell multipleCell = new PdfPCell(); + if (questionType == TypeIfc.MULTIPLE_CHOICE_SURVEY) { + String answerText = selectionBean.getAnswer().getText(); + if (answerText.matches("-?\\d+")) { + multipleCell.setPhrase(new Paragraph(" " + answerText)); + } else { + multipleCell.setPhrase(new Paragraph(" " + rb.getString(this.cleanText(answerText)))); + } + } else { + multipleCell.setPhrase(createLatexParagraph(" " + selectionBean.getAnswer().getLabel() + ". " + this.cleanText(selectionBean.getAnswer().getText()))); + } + multipleCell.setBorderWidth(0); + if (questionType == TypeIfc.MULTIPLE_CORRECT) { + multipleCell.setCellEvent(new CheckboxCellEvent(selectionBean.getResponse())); + } else { + multipleCell.setCellEvent(new CircleCellEvent(selectionBean.getResponse())); + } + PdfPCell finalCell = new PdfPCell(multipleCell); + if (selectionBean.getAnswer().getIsCorrect() != null && selectionBean.getResponse()) { + finalCell.setCellEvent(new CheckOrCrossCellEvent(selectionBean.getAnswer().getIsCorrect())); + } + multipleTable.addCell(finalCell); + document.add(multipleTable); + } else if (questionType == TypeIfc.MATCHING || questionType == TypeIfc.EXTENDED_MATCHING_ITEMS) { + document.add(new Paragraph(" - " + ((String) answer))); + } else if (questionType == TypeIfc.TRUE_FALSE) { + PdfPTable trueFalsequestionTable = new PdfPTable(1); + trueFalsequestionTable.setHorizontalAlignment(PdfPTable.ALIGN_LEFT); + SelectItem selectItem = (SelectItem) answer; + PdfPCell questionCell = new PdfPCell(new Paragraph(" " + selectItem.getLabel())); + questionCell.setBorderWidth(0); + questionCell.setCellEvent(new CircleCellEvent(selectItem.getValue().equals(item.getResponseId()))); + PdfPCell finalCell = new PdfPCell(questionCell); + if (selectItem.getValue().equals(item.getResponseId())) { + finalCell.setCellEvent(new CheckOrCrossCellEvent(StringUtils.equals(selectItem.getDescription(), "true"))); + } + trueFalsequestionTable.addCell(finalCell); + document.add(trueFalsequestionTable); + } + } + List matchingItems = item.getMatchingArray(); + if (matchingItems != null) { + PdfPTable matchingTable = new PdfPTable(1); + matchingTable.setWidthPercentage(100f); + for (Object matchingItem : matchingItems) { + if (questionType == TypeIfc.IMAGEMAP_QUESTION) { + PdfPCell matchingCell = new PdfPCell(new Phrase(rb.getString("item") + " " + ((ImageMapQuestionBean) matchingItem).getText())); + matchingCell.setBorderWidth(0); + matchingCell.setCellEvent(new CheckOrCrossCellEvent((((ImageMapQuestionBean) matchingItem).getIsCorrect() != null)? ((ImageMapQuestionBean) matchingItem).getIsCorrect() : false)); + matchingTable.addCell(matchingCell); + } else if (questionType == TypeIfc.MATCHING || questionType == TypeIfc.EXTENDED_MATCHING_ITEMS) { + for (Object choice : ((MatchingBean) matchingItem).getChoices()) { + if (((MatchingBean) matchingItem).getResponse() != null && ((MatchingBean) matchingItem).getResponse().equals(((SelectItem) choice).getValue())) { + PdfPCell matchingCell = new PdfPCell(new Phrase(((SelectItem) choice).getLabel() + " ··> " + ((MatchingBean) matchingItem).getText())); + matchingCell.setBorderWidth(0); + matchingCell.setCellEvent(new CheckOrCrossCellEvent(((MatchingBean) matchingItem).getIsCorrect())); + matchingTable.addCell(matchingCell); + } + } + + } + } + document.add(matchingTable); + } + + Paragraph paragraph = new Paragraph(); + Font redFont = new Font(); + redFont.setColor(Color.RED); + redFont.setStyle(Font.BOLD); + if (questionType == TypeIfc.CALCULATED_QUESTION) { + paragraph.add(new Phrase(rb.getString("correct_response") + ": ", redFont)); + paragraph.add(new Phrase(item.getKey())); + document.add(paragraph); + } else if (questionType == TypeIfc.FILL_IN_BLANK || questionType == TypeIfc.FILL_IN_NUMERIC) { + paragraph.add(new Phrase(rb.getString("correct_response") + ": ", redFont)); + paragraph.add(new Phrase(item.getKey())); + document.add(paragraph); + } else if (questionType == TypeIfc.ESSAY_QUESTION) { + if (item.getModelAnswerIsNotEmpty()) { + paragraph.add(new Phrase(rb.getString("preview_model_short_answer") + ": ", redFont)); + paragraph.add(new Phrase(item.getKey())); + document.add(paragraph); + } + } else if (questionType != TypeIfc.MULTIPLE_CHOICE_SURVEY && questionType != TypeIfc.MATRIX_CHOICES_SURVEY + && questionType != TypeIfc.FILE_UPLOAD && questionType != TypeIfc.IMAGEMAP_QUESTION + && questionType != TypeIfc.AUDIO_RECORDING) + { + paragraph.add(new Phrase(rb.getString("correct_response") + ": ", redFont)); + paragraph.add(new Phrase(item.getAnswerKeyTF())); + document.add(paragraph); + } + + if (item.getGradingCommentIsNotEmpty() || item.getFeedbackIsNotEmpty()) { + document.add(new Paragraph(rb.getString("comment_for_student"), boldFont)); + document.add(new Paragraph(Chunk.NEWLINE)); + PdfPTable commentTable = new PdfPTable(1); + commentTable.setWidthPercentage(90f); + commentTable.setHorizontalAlignment(PdfPTable.ALIGN_LEFT); + if (item.getGradingCommentIsNotEmpty()) { + PdfPCell commentCell = new PdfPCell(new Paragraph(createLatexParagraph(this.cleanText(item.getGradingComment())))); + commentCell.setMinimumHeight(25f); + commentCell.setPadding(5f); + commentCell.setBorderColor(gray); + commentTable.addCell(commentCell); + } + if (item.getFeedbackIsNotEmpty()) { + PdfPCell commentCell = new PdfPCell(new Paragraph(createLatexParagraph(this.cleanText(item.getFeedback())))); + commentCell.setMinimumHeight(25f); + commentCell.setPadding(5f); + commentCell.setBorderColor(gray); + commentTable.addCell(commentCell); + } + document.add(commentTable); + } + } + } + + outputStream.flush(); + outputStream.close(); + faces.responseComplete(); + } catch (Exception ex) { + log.error(ex.getMessage()); + } finally { + document.close(); + } + + } + + /** + * Method to create a cell using the sent text and configuration and added to the table sent. + * In the configuration variable there are 7 differents configurations. So: + * - 0: create a cell without border, simulating bold black or gray content + * - 1: create a cell without border and bolder text, setting gray color to the background + * - 2: create a cell without border and setting gray colors to the background + * - 3: create a cell and setting gray color to the border and background + * - 4: create a cell, without border and with a special coloured font (black, green, gray or red) + * - 5: create a cell, without border and center align + * - 6: create a cell, without only the top border + * + * And some of these configurations, like the 0, 2, 3 and 4 configuration, has selection of colors: + * - 0. Font colors: + * · Black (color = 1) + * · Gray (color is something else) + * - 2. BackgroundColor colors: + * · Gray (color = 1) + * · Light Gray (color is something else) + * - 3. BackgroundColor colors: + * · Light Gray (color = 1) + * · Default color (color is something else) + * - 4. Font colors: + * · Black (color = 0) + * · Green (color = 1) + * · Gray (color = 2) + * · Red (color is something else) + * + * @param table - PdfPTable + * @param content - String + * @param configuration - int + * @param color - int + */ + private void addCellToTable(PdfPTable table, String content, int configuration, int color) { + PdfPCell cell = new PdfPCell(); + cell.setBorderWidth(0); + switch (configuration) { + case 0: + table.setWidthPercentage(100f); + cell.setPhrase(new Paragraph(content, new Font(Font.TIMES_ROMAN, 12, Font.BOLD, (color == 1)? Color.BLACK : Color.GRAY))); + break; + case 1: + cell.setPhrase(new Paragraph(content, boldFont)); + cell.setMinimumHeight(25f); + cell.setPadding(5f); + cell.setBackgroundColor(grayLight); + break; + case 2: + cell.setPhrase(new Paragraph(content)); + cell.setMinimumHeight(25f); + cell.setPadding(5f); + cell.setBackgroundColor((color == 1)? gray : grayLight); + break; + case 3: + cell.setPhrase(new Paragraph(content)); + cell.setBorderColor(gray); + cell.setBorderWidth(1); + if (color == 1) { + cell.setBackgroundColor(grayLight); + } + cell.setPadding(8f); + break; + case 5: + cell.setPhrase(new Paragraph(content)); + cell.setMinimumHeight(20f); + cell.setHorizontalAlignment(Element.ALIGN_CENTER); + break; + case 6: + cell.setPhrase(new Paragraph(content)); + cell.setBorderWidthTop(1); + cell.setMinimumHeight(20f); + break; + default: + cell.setPhrase(new Paragraph(content)); + break; + } + table.addCell(cell); + } + + /** + * Method to get the question text and parse if has html and get only the table transforming + * it into a PdfPTable + * + * @param questionText - String + * @param showAllInformation - boolean + * @return - PdfPTable + */ + private PdfPTable getQuestionTitle(String questionText, boolean showAllInformation) { + PdfPTable auxTable = new PdfPTable(1); + String[] textSeparatedByLineBreak = questionText.split("
"); + String finalText = ""; + if (textSeparatedByLineBreak.length > 1) { + for (String text : textSeparatedByLineBreak) { + String cleanedText = this.cleanText(text); + if (StringUtils.isNotEmpty(cleanedText)){ + finalText += cleanedText + "\n"; + } + } + } else { + textSeparatedByLineBreak = (questionText.indexOf("\n") != -1? questionText.split("\n") : textSeparatedByLineBreak); + for (String text : textSeparatedByLineBreak) { + String cleanedText = this.cleanText(text); + if (StringUtils.isNotEmpty(cleanedText)){ + finalText += cleanedText + "\n"; + } + } + } + DeliveryBean deliveryBean = (DeliveryBean) ContextUtil.lookupBean("delivery"); + if ((finalText.indexOf(LATEX_SEPARATOR_DOLLAR) != -1 || finalText.indexOf(LATEX_SEPARATOR_START[0]) != -1 || finalText.indexOf(LATEX_SEPARATOR_START[1]) != -1) && deliveryBean.getIsMathJaxEnabled()) { + addLatexFunctionsToTable(finalText, auxTable); + } else { + this.addCellToTable(auxTable, finalText, 0, 1); + } + + if (showAllInformation) { + addTableElementsToTable(questionText, auxTable); + addImageElementsToTable(questionText, auxTable); + } + + PdfPCell finalQuestionCell = new PdfPCell(auxTable); + finalQuestionCell.setBorderWidth(1); + finalQuestionCell.setBorderColor(grayLight); + finalQuestionCell.setPadding(8f); + PdfPTable finalTable = new PdfPTable(1); + finalTable.setWidthPercentage(90f); + finalTable.setHorizontalAlignment(PdfPTable.ALIGN_LEFT); + finalTable.addCell(finalQuestionCell); + + return finalTable; + } + + /** + * Method to add the Table Elements from a String to a PdfPTable + * + * @param text - string that contain the latex functions + * @param table - PdfPTable where save the resolved text + */ + private void addLatexFunctionsToTable(String text, PdfPTable table) { + Paragraph latexParagraph = createLatexParagraph(text); + PdfPCell latexCell = new PdfPCell(latexParagraph); + latexCell.setBorderWidth(0); + table.addCell(latexCell); + } + + /** + * Method to create a Paragraph with Latex functions. + * + * @param text + * @return Paragraph latexParagraph + */ + private Paragraph createLatexParagraph(String text) { + Paragraph latexParagraph = new Paragraph(); + String[] searchIndex = {LATEX_SEPARATOR_DOLLAR, LATEX_SEPARATOR_START[0], LATEX_SEPARATOR_START[1]}; + DeliveryBean deliveryBean = (DeliveryBean) ContextUtil.lookupBean("delivery"); + if ((text.indexOf(searchIndex[0]) != -1 || text.indexOf(searchIndex[1]) != -1 || text.indexOf(searchIndex[2]) != -1) && deliveryBean.getIsMathJaxEnabled()) { + String[] finalSearchIndex = {LATEX_SEPARATOR_DOLLAR, LATEX_SEPARATOR_FINAL[0], LATEX_SEPARATOR_FINAL[1]}; + int currentSearch = 1; + if (text.indexOf(searchIndex[0]) != -1) { + currentSearch = 0; + } + if (text.indexOf(searchIndex[1]) != -1){ + currentSearch = (text.indexOf(searchIndex[0]) != -1 && text.indexOf(searchIndex[0]) < text.indexOf(searchIndex[1]))? 0 : 1; + } else if (text.indexOf(searchIndex[2]) != -1) { + currentSearch = (text.indexOf(searchIndex[0]) != -1 && text.indexOf(searchIndex[0]) < text.indexOf(searchIndex[2]))? 0 : 2; + } + + int latexInitIndex = text.indexOf(searchIndex[currentSearch]); + int latexFinalIndex = text.indexOf(finalSearchIndex[currentSearch], latexInitIndex + 2); + while (latexInitIndex != -1 && latexFinalIndex != -1) { + String textBeforeLatex = text.substring(0, latexInitIndex); + String latex = text.substring(latexInitIndex + 2, latexFinalIndex).replace(searchIndex[currentSearch], "").replace(finalSearchIndex[currentSearch], ""); + TeXFormula formula = new TeXFormula(latex); + Image pdfLatexImage = null; + try { + pdfLatexImage = Image.getInstance(formula.createBufferedImage(TeXFormula.BOLD, 300, null, null), null); + } catch (Exception ex) { + log.error(ex.getMessage()); + } + float finalWidth = formula.createBufferedImage(TeXFormula.BOLD, 10, null, null).getWidth(null); + float finalHeight = formula.createBufferedImage(TeXFormula.BOLD, 10, null, null).getHeight(null); + pdfLatexImage.scaleAbsolute(finalWidth, finalHeight); + + latexParagraph.add(new Chunk(textBeforeLatex)); + latexParagraph.add(new Chunk(pdfLatexImage, -1, -2, true)); + + currentSearch = 1; + if (text.indexOf(searchIndex[0], latexFinalIndex + 1) != -1) { + currentSearch = 0; + if (text.indexOf(searchIndex[1], latexFinalIndex + 2) != -1) { + currentSearch = text.indexOf(searchIndex[0], latexFinalIndex + 2) < text.indexOf(searchIndex[1], latexFinalIndex + 2) ? 0 : 1; + } else if (text.indexOf(searchIndex[2], latexFinalIndex + 2) != -1) { + currentSearch = text.indexOf(searchIndex[0], latexFinalIndex + 2) < text.indexOf(searchIndex[2], latexFinalIndex + 2)? 0 : 2; + } + } + + latexInitIndex = text.indexOf(searchIndex[currentSearch], latexFinalIndex + 1); + + if (latexInitIndex != -1) { + textBeforeLatex = text.substring(latexFinalIndex, latexInitIndex).replace(LATEX_SEPARATOR_DOLLAR, "") + .replace(LATEX_SEPARATOR_START[0], "").replace(LATEX_SEPARATOR_FINAL[0], "") + .replace(LATEX_SEPARATOR_START[1], "").replace(LATEX_SEPARATOR_FINAL[1], ""); + latexFinalIndex = text.indexOf(finalSearchIndex[currentSearch], latexInitIndex + 2); + } + } + latexParagraph.add(new Chunk(text.substring(latexFinalIndex).replace(LATEX_SEPARATOR_DOLLAR, "").replace(LATEX_SEPARATOR_START[0], "") + .replace(LATEX_SEPARATOR_FINAL[0], "").replace(LATEX_SEPARATOR_START[1], "").replace(LATEX_SEPARATOR_FINAL[1], ""))); + } else { + latexParagraph.add(new Chunk(text)); + } + return latexParagraph; + } + + /** + * Method to add the Table Elements from a String to a PdfPTable + * + * @param text - string that contain the Table Elements functions + * @param table - PdfPTable where save the resolved text + */ + private void addTableElementsToTable(String text, PdfPTable table){ + try { + Elements tables = Jsoup.parse(text).select("table"); + for (org.jsoup.nodes.Element tableElement : tables) { + PdfPTable pdfTable = new PdfPTable(tableElement.select("tr").first().children().size()); + for (org.jsoup.nodes.Element row : tableElement.select("tr")) { + for (org.jsoup.nodes.Element cell : row.children()) { + PdfPCell contentCell = new PdfPCell(new Paragraph(cell.text())); + contentCell.setBorderWidth(0); + contentCell.setBorderWidthBottom(1); + + contentCell.setPadding(5f); + pdfTable.addCell(contentCell); + } + } + PdfPCell questionCell = new PdfPCell(pdfTable); + questionCell.setBorderWidth(0); + table.addCell(questionCell); + PdfPCell blankLine = new PdfPCell(new Paragraph(Chunk.NEWLINE)); + blankLine.setBorderWidth(0); + table.addCell(blankLine); + } + } catch (Exception ex) { + log.error(ex.getMessage()); + } + } + + /** + * Method to add the Image Elements from a String to a PdfPTable + * + * @param text - string that contain the Image Elements functions + * @param table - PdfPTable where save the resolved text + */ + private void addImageElementsToTable(String text, PdfPTable table){ + try { + Elements imageElements = Jsoup.parse(text).select("img"); + for (org.jsoup.nodes.Element imageElement : imageElements) { + String imageSrc = imageElement.attr("src"); + Image image = Image.getInstance(contentHostingService.getResource(imageSrc.replace(ServerConfigurationService.getAccessUrl() + "/content", "")).getContent()); + float originalWidth = image.getWidth(); + float originalHeight = image.getHeight(); + float newHeight = PageSize.A4.getHeight() * 0.25f; + float newWidth = (originalWidth * newHeight) / originalHeight; + if (newWidth > (PageSize.A4.getWidth() * 0.8)) { + image.scalePercent((PageSize.A4.getWidth() / originalWidth) * 80); + } else { + image.scaleAbsoluteHeight(newHeight); + image.scaleAbsoluteWidth(newWidth); + } + PdfPCell imageCell = new PdfPCell(image); + imageCell.setBorderWidth(0); + table.addCell(imageCell); + } + } catch (Exception ex) { + log.error(ex.getMessage()); + } + } + + /** + * Method to get only the text without the html tag + * + * @param text - String + * @return - String + */ + private String cleanText(String text) { + String textAux = text; + int tableIndex = textAux.indexOf("", tableIndex); + if (tableIndex != -1) { + isTable = true; + } + if (isTable) { + if (tableIndexFinal != -1) { + if (tableIndex != -1) { + while (tableIndex != -1) { + textAux = (textAux.substring(0, tableIndex) + textAux.substring(tableIndexFinal)); + tableIndex = textAux.indexOf("", tableIndex); + } + isTable = false; + } else { + isTable = false; + } + + } else { + textAux = ""; + } + } + return Jsoup.parse(textAux).text(); + } + + /** + * Method to process the fill in questions text + * + * @param Document - document + * @param List - fillInArray + * @param boolean - numeric + */ + private void processFillInQuestion(Document document, List fillInArray, boolean numeric) throws Exception { + PdfPTable fillInTable = new PdfPTable(1); + fillInTable.setHorizontalAlignment(PdfPTable.ALIGN_LEFT); + int i = 0; + String questionText = ""; + for (Object fillInObject : fillInArray) { + if (numeric) { + if (i + 1 != fillInArray.size()) { + questionText += ((FinBean) fillInObject).getText() + "(" + (++i) + ")"; + PdfPCell fillInCell = new PdfPCell(new Phrase((i) + ". " + (StringUtils.equals(((FinBean) fillInObject).getResponse(), "")? rb.getString("no_answer.text") : ((FinBean) fillInObject).getResponse()))); + fillInCell.setBorderWidth(0); + fillInCell.setCellEvent(new CheckOrCrossCellEvent(((FinBean) fillInObject).getIsCorrect()!= null && ((FinBean) fillInObject).getIsCorrect())); + fillInTable.addCell(fillInCell); + } else { + questionText += ((FinBean) fillInObject).getText(); + } + } else { + if (i + 1 != fillInArray.size()) { + questionText += ((FibBean) fillInObject).getText() + "(" + (++i) + ")"; + PdfPCell fillInCell = new PdfPCell(new Phrase((i) + ". " + (StringUtils.equals(((FibBean) fillInObject).getResponse(), "")? rb.getString("no_answer.text") : ((FibBean) fillInObject).getResponse()))); + fillInCell.setBorderWidth(0); + fillInCell.setCellEvent(new CheckOrCrossCellEvent(((FibBean) fillInObject).getIsCorrect()!= null && ((FibBean) fillInObject).getIsCorrect())); + fillInTable.addCell(fillInCell); + } else { + questionText += ((FibBean) fillInObject).getText(); + } + } + } + document.add(this.getQuestionTitle(questionText, true)); + document.add(fillInTable); + } + + /** + * Class to handle the CircleCellEvent (equivalent to the radio in html) + */ + private static class CircleCellEvent implements PdfPCellEvent { + private boolean checked = false; + private boolean centered = false; + + public CircleCellEvent(boolean checked) { + this.checked = checked; + } + public CircleCellEvent(boolean checked, boolean centered) { + this.checked = checked; + this.centered = centered; + } + + public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) { + PdfContentByte canvas = canvases[PdfPTable.TEXTCANVAS]; + float xAux = (centered) ? (position.getRight() - position.getLeft()) / 2 + position.getLeft() + 1 : position.getLeft() + 1; + float yAux = position.getTop() - 10; + float radius = 5f; + + canvas.circle(xAux, yAux, radius); + canvas.setColorFill(Color.BLACK); + canvas.fill(); + canvas.circle(xAux, yAux, radius * 0.95f); + canvas.setColorFill(Color.WHITE); + canvas.fill(); + if (checked) { + canvas.circle(xAux, yAux, radius * 0.6f); + canvas.setColorFill(Color.BLACK); + canvas.fill(); + } + + canvas.setColorFill(Color.BLACK); + canvas.setColorStroke(Color.BLACK); + } + } + + /** + * Class to handle the CheckboxCellEvent (equivalent to the checkbox in html) + */ + private static class CheckboxCellEvent implements PdfPCellEvent { + private boolean checked = false; + + public CheckboxCellEvent(boolean checked) { + this.checked = checked; + } + + public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) { + PdfContentByte canvas = canvases[PdfPTable.TEXTCANVAS]; + float xAux = position.getLeft() - 1; + float yAux = position.getTop() - 14; + + canvas.rectangle(xAux, yAux, 8, 8); + canvas.stroke(); + + if (checked) { + canvas.moveTo(xAux + 1.5f, yAux + 1.5f); + canvas.lineTo(xAux + 6.5f, yAux + 6.5f); + canvas.moveTo(xAux + 1.5f, yAux + 6.5f); + canvas.lineTo(xAux + 6.5f, yAux + 1.5f); + canvas.stroke(); + } + } + } + + /** + * Class to handle the ImageMapQuestionCellEvent + */ + private static class ImageMapQuestionCellEvent implements PdfPCellEvent { + private ArrayList answerRectangles = new ArrayList(); + private ArrayList answerCircles = new ArrayList(); + private float originalWidth; + private float originalHeight; + + public ImageMapQuestionCellEvent(ArrayList answerCircles, ArrayList answerRectangles, float originalWidth, float originalHeight) { + this.answerRectangles = answerRectangles; + this.answerCircles = answerCircles; + this.originalWidth = originalWidth; + this.originalHeight = originalHeight; + } + + public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) { + PdfContentByte canvas = canvases[PdfPTable.TEXTCANVAS]; + float x = position.getLeft(); + float y = position.getBottom(); + + float scaleX = position.getWidth() / (originalWidth); + float scaleY = position.getHeight() / (originalHeight); + for (Rectangle answerRectangle : answerRectangles) { + PdfGState transparentState = new PdfGState(); + transparentState.setFillOpacity(0.65f); + transparentState.setStrokeOpacity(1f); + float transformedX = x + answerRectangle.getLeft() * scaleX; + float transformedY = y + position.getHeight() - answerRectangle.getTop() * scaleY; + float transformedW = answerRectangle.getWidth() * scaleX; + float transformedH = answerRectangle.getHeight() * scaleY; + canvas.rectangle(transformedX, transformedY, transformedW, transformedH); + canvas.setColorFill(Color.BLUE); + canvas.setColorStroke(Color.BLACK); + canvas.setGState(transparentState); + } + canvas.fillStroke(); + canvas.fill(); + + float radius = 3f; + int smallestValue = (answerCircles.size() > 0)? answerCircles.get(0).getPublishedItemId() + 1 : 0; + for (Circle answerCircle : answerCircles) { + PdfGState transparentState = new PdfGState(); + transparentState.setFillOpacity(0.3f); + transparentState.setStrokeOpacity(0.8f); + float transformedX = x + answerCircle.getX() * scaleX; + float transformedY = y + position.getHeight() - answerCircle.getY() * scaleY; + if (answerCircle.getX() != 0 && answerCircle.getY() != 0) { + canvas.circle(transformedX, transformedY, radius); + canvas.setGState(transparentState); + canvas.circle(transformedX, transformedY, 0.3f); + canvas.setColorFill(Color.YELLOW); + canvas.setColorStroke(Color.YELLOW); + } + if (answerCircle.getPublishedItemId() < smallestValue) { + smallestValue = answerCircle.getPublishedItemId(); + } + } + canvas.fillStroke(); + canvas.fill(); + + int questionIndex = 1; + for (Rectangle answerRectangle : answerRectangles) { + PdfGState transparentState = new PdfGState(); + transparentState.setFillOpacity(1f); + transparentState.setStrokeOpacity(1f); + float transformedX = x + answerRectangle.getLeft() * scaleX; + float transformedY = y + position.getHeight() - answerRectangle.getTop() * scaleY; + float transformedW = answerRectangle.getWidth() * scaleX; + float transformedH = answerRectangle.getHeight() * scaleY; + canvas.setGState(transparentState); + try { + canvas.beginText(); + canvas.setColorFill(Color.BLUE); + canvas.setFontAndSize(BaseFont.createFont(), 9); + canvas.showTextAligned(Element.ALIGN_LEFT, String.valueOf(questionIndex), transformedX + transformedW + 1, transformedY, 0); + canvas.endText(); + questionIndex++; + } catch (Exception ex) { + log.error("Cannot write the number of the ImageMap. " + ex.getMessage()); + } + } + + int toReduce = smallestValue; + for (Circle answerCircle : answerCircles) { + float transformedX = x + answerCircle.getX() * scaleX; + float transformedY = y + position.getHeight() - answerCircle.getY() * scaleY; + if (answerCircle.getX() != 0 && answerCircle.getY() != 0) { + try { + int answerIndex = answerCircles.size() - (answerCircle.getPublishedItemId() - toReduce); + canvas.beginText(); + canvas.setColorFill(Color.YELLOW); + canvas.setFontAndSize(BaseFont.createFont(), 9); + canvas.showTextAligned(Element.ALIGN_LEFT, String.valueOf(answerIndex), transformedX + 4, transformedY - 3, 0); + canvas.endText(); + canvas.fill(); + } catch (Exception ex) { + log.error("Cannot write the number of the ImageMap. " + ex.getMessage()); + } + } + } + + PdfGState transparentState = new PdfGState(); + transparentState.setFillOpacity(1f); + transparentState.setStrokeOpacity(1f); + canvas.setGState(transparentState); + canvas.setColorFill(Color.BLACK); + canvas.setColorStroke(Color.BLACK); + } + } + + /** + * Class to handle the CheckOrCrossCellEvent (equivalent to the check and cross icon) + */ + private static class CheckOrCrossCellEvent implements PdfPCellEvent { + private boolean isCheckIcon = false; + + public CheckOrCrossCellEvent(boolean isCheckIcon) { + this.isCheckIcon = isCheckIcon; + } + + public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) { + PdfContentByte canvas = canvases[PdfPTable.TEXTCANVAS]; + float xAux = position.getLeft() - (isCheckIcon? 12 : 10); + float yAux = position.getTop() - (isCheckIcon? 12 : 10); + canvas.setLineWidth(2.5f); + + if (isCheckIcon) { + canvas.setColorFill(new Color(9, 215, 71)); + canvas.setColorStroke(new Color(9, 215, 71)); + canvas.moveTo(xAux - 3, yAux + 3); + canvas.lineTo(xAux, yAux); + canvas.lineTo(xAux + 6, yAux + 5); + } else { + canvas.setColorFill(Color.RED); + canvas.setColorStroke(Color.RED); + canvas.moveTo(xAux, yAux); + canvas.lineTo(xAux - 3, yAux + 3); + canvas.lineTo(xAux + 3, yAux - 3); + canvas.moveTo(xAux, yAux); + canvas.lineTo(xAux + 3, yAux + 3); + canvas.lineTo(xAux - 3, yAux - 3); + } + canvas.stroke(); + canvas.setLineWidth(1f); + canvas.setColorFill(Color.BLACK); + canvas.setColorStroke(Color.BLACK); + } + } + + /** + * Class used to facilitate the use of the circles in the ImageMapQuestionCellEvent + */ + private class Circle { + @Setter @Getter + private float x; + @Setter @Getter + private float y; + @Setter @Getter + private int publishedItemId; + + public Circle(float x, float y, int publishedItemId) { + this.x = x; + this.y = y; + this.publishedItemId = publishedItemId; + } + } + +} diff --git a/samigo/samigo-app/src/webapp/css/tool_sam.css b/samigo/samigo-app/src/webapp/css/tool_sam.css index 6250786b9111..d3192e571205 100644 --- a/samigo/samigo-app/src/webapp/css/tool_sam.css +++ b/samigo/samigo-app/src/webapp/css/tool_sam.css @@ -1522,3 +1522,7 @@ fieldset.short-summary-box { padding: 2px; border: 1px #d9d7d7 solid; } + +input[type="submit"].print_button { + font-size: 14px; +} diff --git a/samigo/samigo-app/src/webapp/jsf/evaluation/gradeStudentResult.jsp b/samigo/samigo-app/src/webapp/jsf/evaluation/gradeStudentResult.jsp index 464d620107cc..aa14d6247ac3 100755 --- a/samigo/samigo-app/src/webapp/jsf/evaluation/gradeStudentResult.jsp +++ b/samigo/samigo-app/src/webapp/jsf/evaluation/gradeStudentResult.jsp @@ -150,7 +150,12 @@ function toPoint(id)

- +
+ + + + +