<?php
namespace App\Controller;
use App\Entity\DataLine;
use App\Entity\DataLines;
use App\Entity\DataSet;
use App\Entity\Results;
use App\Form\DataLinesType;
use App\Form\DataSetType;
use Exception;
use Psr\Log\LoggerInterface;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Dompdf\Dompdf;
use Dompdf\Options;
use Symfony\Contracts\Translation\TranslatorInterface;
use ZipArchive;
class DefaultController extends AbstractController
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* @Route("/", name="home")
* @Route("/presentation", name="presentation")
*/
public function index()
{
return $this->render('index.html.twig');
}
/**
* @Route("/bmd", name="bmd")
*/
public function analyze(Request $request)
{
$session = $request->getSession();
$dataset = new DataSet();
$datalines = new DataLines();
$dataset->setId(random_int(0, 255));
$session->set('dataset', $dataset);
$form = $this->createForm(DataSetType::class, $dataset);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if (is_dir($user_folder = $this->getParameter('upload_directory') . "/" . $request->getSession()->getId())) {
array_map('unlink', glob("$user_folder/*"));
rmdir($user_folder);
}
mkdir($user_folder);
if ($dataset->getDataLoadMethod() === FALSE && !empty($dataset->getDataFile())) {
/** @var UploadedFile $file */
$file = $form->get('datafile')->getData();
$fileName = "data.in";
try {
$file->move(
$user_folder,
$fileName
);
} catch (FileException $e) {
// ... handle exception if something happens during file upload
$this->addFlash('primary', $e->getMessage());
}
$dataset->setDataFile($fileName);
try {
if ($handle = fopen($user_folder . "/" . $dataset->getDataFile(), "r")) {
$delimiters = array(
';' => 0,
',' => 0,
"\t" => 0,
"|" => 0
);
$firstLine = fgets($handle);
foreach ($delimiters as $delimiter => &$count) {
$count = count(str_getcsv($firstLine, $delimiter));
}
while (($data = fgetcsv($handle, "0", array_search(max($delimiters), $delimiters))) !== false) {
$num = count($data);
//si la ligne ne contient pas toutes les informations (ex: ligne vide) on l'ignore
if ($num < 3) continue;
$dataline = new DataLine();
for ($c = 0; $c < $num; $c++) {
$dataline->setConcentration((int)$data[0]);
$dataline->setSubjects((int)$data[1]);
$dataline->setIncidence((int)$data[2]);
if ($dataset->getExposureType() === "several_durations") {
$dataline->setTime((int)$data[3]);
}
}
$datalines->addDataLine($dataline);
}
$dataset->setDataLines($datalines);
fclose($handle);
} else {
throw new Exception ('The datafile can not be read.');
}
} catch (Exception $exception) {
$this->addFlash('secondary', $exception->getMessage());
}
} elseif ($dataset->getDataLoadMethod() === TRUE && !empty($numberOfGroups = $dataset->getNumberOfGroups())) {
for ($c = 0; $c < $numberOfGroups; $c++) {
$dataline = new DataLine();
$dataline->setConcentration(0);
$dataline->setSubjects(0);
$dataline->setIncidence(0);
if ($dataset->getExposureType() === "several_durations") {
$dataline->setTime(0);
}
$datalines->addDataLine($dataline);
}
$dataset->setDataLines($datalines);
}
return $this->redirectToRoute('bmdArray', ['datasetId' => $dataset->getId()]);
}
return $this->render('bmd.html.twig', [
'form' => $form->createView(),
]);
}
/**
* @Route("/bmd/{datasetId}", name="bmdArray")
*/
public function bmdArray(Request $request)
{
$session = $request->getSession();
$dataset = $session->get('dataset');
$datalines = $dataset->getDataLines();
$session_id = $request->getSession()->getId();
$upload_directory = $this->getParameter('upload_directory');
$exposure_type = $dataset->getExposureType();
$form = $this->createForm(DataLinesType::class, $datalines);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if ($exposure_type === 'several_durations') {
$r_file = 'bmd_plusieursDureesExpo_V2.r';
$time = $dataset->getExposureDuration();
} else {
$r_file = 'bmd_uneDureeExpo_V2.r';
$dataset->getExposureDuration() ? $time = $dataset->getExposureDuration() : $time = "NA";
}
$user_folder = $upload_directory . '/' . $session_id;
$plotParameters = $dataset->getPlotParameters();
(in_array("plot_option_log_scale", $plotParameters)) ? $log_scale = TRUE : $log_scale = FALSE;
(in_array("plot_option_error_bars", $plotParameters)) ? $error_bars = TRUE : $error_bars = FALSE;
$replace = array(
str_replace('\\', '/', '"' . $user_folder . '/"'),
"NA",
$dataset->getBmdExtrapolation() === true ? "TRUE" : "FALSE",
$log_scale === true ? "TRUE" : "FALSE",
$error_bars === true ? "TRUE" : "FALSE",
'"' . implode(",", $dataset->getBenchmarkResponse()) . '"',
$time
);
//chemin d'acces à configurer une fois l'application mise sur le serveur
$template_r_file = $this->getParameter('util_directory') . '/' . $r_file;
$fp = fopen($user_folder . '/data.in', 'w');
$headers = array('concentration', 'subjects', 'incidence');
if ($exposure_type === 'several_durations') $headers[] = 'time';
fputcsv($fp, $headers, ';');
foreach ($datalines->getDataLines() as $dataline) {
$data = array($dataline->getConcentration(), $dataline->getSubjects(), $dataline->getIncidence());
if ($exposure_type === 'several_durations') $data[] = $dataline->getTime();
fputcsv($fp, $data, ';');
}
fclose($fp);
ini_set('max_execution_time', 600);
$stopwatch = new Stopwatch();
$target = dirname(dirname(dirname(dirname(dirname(__FILE__)))));
$target .= 'tmp-perso\R-Portable\App\R-Portable\bin\R.exe';
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$cmd = '"' . $target . '" --vanilla --slave --args ' . implode(" ", $replace) . ' < ' . $template_r_file;
$stopwatch->start('RscriptExecution');
exec($cmd);
} else {
$stopwatch->start('RscriptExecution');
//Rediriger la sortie standard vers /dev/null, puis rediriger la sortie d'erreur vers la sortie standard.
//dump("cat " . $template_r_file . " | /opt/R/3.6.0/bin/R --vanilla --slave --args " . implode(" ", $replace) . " >/dev/null 2>&1");die;
exec("cat " . $template_r_file . " | /opt/R/3.6.0/bin/R --vanilla --slave --args " . implode(" ", $replace) . " >/dev/null 2>&1");
}
while (!is_file($user_folder . '/results.txt')) {
continue;
}
$event = $stopwatch->stop('RscriptExecution');
$session->set('timer', $event->getDuration());
//Enregistrement des executions dans un fichier de log
$file = dirname(dirname(dirname(__FILE__))) . "/log.txt";
if (!file_exists($file)) touch($file);
$current = file_get_contents($file);
$current .= "\n Script execute - " . date("Y-m-d H:i:s");
file_put_contents($file, $current);
return $this->redirectToRoute('resultsByUniqueId', ['datasetId' => $dataset->getId()]);
}
return $this->render('bmd_array.html.twig', [
'form' => $form->createView(),
'dataset' => $dataset,
]);
}
/**
* @Route("/results/{datasetId}", name="resultsByUniqueId")
*/
public function results(Request $request, TranslatorInterface $translator)
{
$session = $request->getSession();
$dataset = $session->get('dataset');
$session_id = $request->getSession()->getId();
$upload_directory = $this->getParameter('upload_directory');
$user_folder = "$upload_directory/$session_id/";
$exposure_type = $dataset->getExposureType();
$results_file = $user_folder . 'results.txt';
$warning_file = $user_folder . 'warnings.txt';
if (is_file($results_file)) {
$results = new Results();
$results->setDataset($dataset->getId());
$tab_result = file($results_file);
for ($i = 0; $i < count($tab_result); $i++) {
$char[$i] = explode("\t", $tab_result[$i]);
}
if ($char[0][0] == '"slope"') {
$results->setErrors(null);
} else {
$results->setErrors(file_get_contents($results_file));
}
// en cas d'erreur dans le script R retour sur l'écran de saisie et affichage des erreurs
$error_list = [
"Please check your data: There should be 3 columns named concentration, subjects, incidence",
"Please check your data: all data should be numeric.",
"Please check your data: number of data for dose, incidence, and subjects should be equal",
"Please check your data and delete any line with missing data",
"Exposure duration should be a number :",
"Number of groups is not sufficient for log-probit modeling: there should be at least 3",
"Incidence rate must be between 0 and 1",
"There must be incidence rates strictly between 0 and 1",
"Failed to estimate parameters: algorithm did not converge",
"Please check your data: There should be 4 columns named concentration, subjects, incidence, time",
"Please check your data: number of data for dose, incidence, subjects, and duration should be equal",
"Number of groups is not sufficient for log-probit modeling",
"All durations are equal: please select the 'One duration' option",
"At least one of the exposure durations must be tested for several concentrations or at least one of the concentrations must be tested at several exposure durations",
"There must be at least two incidence rates strictly between 0 and 1, for two different durations",
];
if ($results->getErrors()) {
$string = $results->getErrors();
$this->logger->error('Error found: ', [
'$string' => $string,
]);
$extracted_string = "error";
$first_colon_pos = strpos($string, ':');
if ($first_colon_pos !== false) {
$extracted_string = trim(substr($string, $first_colon_pos + 1));
}
// Check if $extracted_string matches any of the errors in $error_list
$error_found = false;
foreach ($error_list as $error) {
if (strpos($extracted_string, $error) !== false) {
$error_found = true;
break;
}
}
$this->logger->error('Error found: ' . $error, [
'extracted_string' => $extracted_string,
]);
if (!$error_found) {
// Create a ZIP archive of all files in $user_folder
$zip = new ZipArchive();
$zip_filename = "../data/files_$session_id.zip";
if ($zip->open($zip_filename, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) {
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($user_folder),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $name => $file) {
if (!$file->isDir()) {
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($user_folder) + 1);
$zip->addFile($filePath, $relativePath);
}
}
$zip->close();
}
// Affichage d'une erreur générique
$extracted_string = $translator->trans('results.technical_error');
}
$this->addFlash('danger', $translator->trans('results.error') . $extracted_string . $translator->trans('results.error_contact'));
return $this->redirectToRoute('bmd');
}
if ($char[1][11] != 0) {
$results->setConvergence("not converged " . $char[1][11]);
} else {
$results->setConvergence("converged");
$results->setSlope(array($char[1][1], $char[1][2], $char[1][3]));
$pictures = array();
$scaledResiduals = array();
$computations = array();
if ($exposure_type === 'several_durations') {
$results->setN(array($char[1][4], $char[1][5], $char[1][6]));
$results->setIntercept(array($char[1][7], $char[1][8], $char[1][9]));
array_push($pictures, base64_encode(file_get_contents(__DIR__ . '/../../public/uploads/' . $session_id . '/plot_allT.jpg')));
for ($i = 1; $i <= (($char[1][19]) * ($char[1][20])); $i++) {
$plot_file = $user_folder . 'plot' . $char[$i][13] . '_T' . $char[$i][12] . '.jpg';
if (@is_file($plot_file)) {
array_push($pictures, base64_encode(file_get_contents(__DIR__ . '/../../public/uploads/' . $session_id . '/plot' . $char[$i][13] . '_T' . $char[$i][12] . '.jpg')));
}
}
$results->setMaxLog($char[1][10]);
for ($i = 1; $i <= ($char[1][23]); $i++) {
array_push($scaledResiduals, $char[1][23 + $i]);
}
$results->setChiSquare($char[1][21]);
$results->setFitGoodness($char[1][18]);
for ($i = 1; $i <= (($char[1][19]) * ($char[1][20])); $i++) {
$computations[$i][] = $char[$i][13];
$computations[$i][] = $char[$i][12];
$computations[$i][] = $char[$i][14];
$computations[$i][] = $char[$i][15];
$computations[$i][] = $char[$i][17];
}
} else {
$results->setN(null);
$results->setIntercept(array($char[1][4], $char[1][5], $char[1][6]));
for ($i = 1; $i <= $char[1][15]; $i++) {
array_push($pictures, base64_encode(file_get_contents(__DIR__ . '/../../public/uploads/' . $session_id . '/plot' . $char[$i][13] . '.jpg')));
}
$results->setMaxLog($char[1][7]);
for ($i = 1; $i <= ($char[1][33]); $i++) {
array_push($scaledResiduals, $char[1][33 + $i]);
}
$results->setChiSquare($char[1][16]);
$results->setFitGoodness($char[1][14]);
for ($i = 1; $i <= $char[1][15]; $i++) {
$computations[$i][] = $char[$i][13];
$computations[$i][] = $char[$i][9];
$computations[$i][] = $char[$i][10];
$computations[$i][] = $char[$i][12];
}
$time = $dataset->getExposureDuration();
if (isset($time) && $dataset->getBmdExtrapolation() === true) {
$duration = array(1, 10, 20, 30, 60, 120, 240, 480);
$extrapolations = array();
for ($i = 1; $i <= $char[1][15]; $i++) {
foreach ($duration as $key => $value) {
$extrapolations[$i][$value][] = $char[$i][13];
$extrapolations[$i][$value][] = $value;
$extrapolations[$i][$value][] = $char[$i][$key + 17];
$extrapolations[$i][$value][] = $char[$i][$key + 25];
}
}
$results->setBenchmarkDoseExtrapolation($extrapolations);
}
}
$results->setPictures($pictures);
$results->setScaledResiduals($scaledResiduals);
$results->setBenchmarkDoseComputation($computations);
}
if (is_file($warning_file)) {
$tab_warning = file($warning_file);
for ($i = 0; $i < count($tab_warning); $i++) {
$char2[$i] = explode("\t", $tab_warning[$i]);
}
$results->setWarnings(file_get_contents($warning_file));
}
} else {
$this->addFlash('danger', "Results file can not be obtained");
}
$pdfOptions = new Options();
$pdfOptions->setIsRemoteEnabled(true);
$pdfOptions->setDefaultFont("DejaVu Sans");
$dompdf = new Dompdf($pdfOptions);
$html = $this->renderView('pdf.html.twig', [
'dataset' => $dataset,
'results' => $results,
]);
$dompdf->loadHtml($html);
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();
$output = $dompdf->output();
$studyReference = $dataset->getStudyReference() ? $dataset->getStudyReference() . '-' : '';
$species = $dataset->getSpecies() ? $dataset->getSpecies() . '-' : '';
$filePath = $user_folder . '/PROBIT-BMD-' . $studyReference . $species;
file_put_contents($filePath . 'results.pdf', $output);
file_put_contents($filePath . 'data.txt', str_replace("\n", "\r\n", file_get_contents($user_folder . '/data.in')));
return $this->render('results.html.twig', [
'dataset' => $dataset,
'results' => $results,
]);
}
/**
* @Route("/help", methods={"GET"}, name="help")
*/
public function help()
{
return $this->render('help.html.twig');
}
}