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)
-
+
+
+
+
+
+