ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
class.ilTestArchiver.php
Go to the documentation of this file.
1<?php
2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3
16{
17 #region Constants / Config
18
19 const DIR_SEP = '/';
20
21 const HTML_SUBMISSION_FILENAME = 'test_submission.html';
22 const PDF_SUBMISSION_FILENAME = 'test_submission.pdf';
23 const PASS_MATERIALS_PATH_COMPONENT = 'materials';
25
26 const TEST_BEST_SOLUTION_PATH_COMPONENT = 'best_solution';
27 const HTML_BEST_SOLUTION_FILENAME = 'best_solution.html';
28 const PDF_BEST_SOLUTION_FILENAME = 'best_solution.pdf';
29 const TEST_MATERIALS_PATH_COMPONENT = 'materials';
30
31 const TEST_RESULT_FILENAME = 'test_result_v';
32 const TEST_RESULT_POSTFIX = '.pdf';
33
34 const TEST_OVERVIEW_PDF_FILENAME = 'results_overview_html_v';
36
37 const TEST_OVERVIEW_HTML_FILENAME = 'results_overview_pdf_v';
39
40 const LOG_DTSGROUP_FORMAT = 'D M j G:i:s T Y';
41 const LOG_ADDITION_STRING = ' Adding ';
42 const LOG_CREATION_STRING = ' Creating ';
43 const LOG_UPDATE_STRING = ' Updating ';
44 const LOG_DELETION_STRING = ' Deleting ';
45
46 const TEST_LOG_FILENAME = 'test.log';
47 const DATA_INDEX_FILENAME = 'data_index.csv';
48 const ARCHIVE_LOG = 'archive.log';
49
50 const EXPORT_DIRECTORY = 'archive_exports';
51
52 #endregion
53
54 /*
55 * Test-Archive Schema:
56 *
57 * <external directory>/<client>/tst_data/archive/tst_<obj_id>/
58 * - archive_data_index.dat
59 * - archive_log.log
60 *
61 * - test_log.log
62 *
63 * - test_results_v<n>.pdf
64 * - test_results_v<n>.csv
65 *
66 * -> best_solution/
67 * best_solution_v<n>.pdf
68 * -> /materials/q_<question_fi>/<n>_<filename>
69 *
70 * -> <year>/<month>/<day>/<ActiveFi>_<Pass>[_<Lastname>][_<Firstname>][_<Matriculation>]/
71 * -> test_submission.pdf
72 * -> test_submission.html
73 * -> test_submission.sig (et al)
74 * -> test_result_v<n>.pdf
75 * -> /materials_v<n>/<question_fi>/<n>_<filename>
76 */
77
78 #region Properties
79
81 protected $client_id;
82 protected $test_obj_id;
85 protected $ilDB;
91
92 #endregion
93
99 public function __construct($test_obj_id)
100 {
102 global $ilias;
103 $this->external_directory_path = $ilias->ini_ilias->readVariable('clients', 'datadir');
104 $this->client_id = $ilias->client_id;
105 $this->test_obj_id = $test_obj_id;
106 $this->ilDB = $ilias->db;
107
108 $this->archive_data_index = $this->readArchiveDataIndex();
109
110 $this->participantData = null;
111 }
112
116 public function getParticipantData()
117 {
119 }
120
125 {
126 $this->participantData = $participantData;
127 }
128
129 #region API methods
130
143 public function handInParticipantSubmission($active_fi, $pass, $pdf_path, $html_string)
144 {
146 $this->ensurePassDataDirectoryIsAvailable($active_fi, $pass);
147
148 $pdf_new_path = $this->getPassDataDirectory($active_fi, $pass) . self::DIR_SEP
150 copy($pdf_path, $pdf_new_path);
151 # /home/mbecker/public_html/ilias/trunk-primary/extern/default/tst_data/archive/tst_350/2013/09/19/80_1_root_user_/test_submission.pdf
152 $html_new_path = $this->getPassDataDirectory($active_fi, $pass) . self::DIR_SEP
154 file_put_contents($html_new_path, $html_string);
155
156 $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $pdf_new_path);
157 $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $html_new_path);
158 }
159
169 public function handInParticipantQuestionMaterial($active_fi, $pass, $question_fi, $original_filename, $file_path)
170 {
172 $this->ensurePassDataDirectoryIsAvailable($active_fi, $pass);
173
174 $pass_question_directory = $this->getPassDataDirectory($active_fi, $pass)
175 . self::DIR_SEP . self::QUESTION_PATH_COMPONENT_PREFIX . $question_fi;
176 if (!is_dir($pass_question_directory)) {
177 mkdir($pass_question_directory, 0777, true);
178 }
179
180 copy($file_path, $pass_question_directory . self::DIR_SEP . $original_filename);
181
182 $this->logArchivingProcess(
183 date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING
184 . $pass_question_directory . self::DIR_SEP . $original_filename
185 );
186 }
187
198 public function handInParticipantMisc($active_fi, $pass, $original_filename, $file_path)
199 {
201 $this->ensurePassDataDirectoryIsAvailable($active_fi, $pass);
202 $new_path = $this->getPassDataDirectory($active_fi, $pass) . self::DIR_SEP . $original_filename;
203 copy($file_path, $new_path);
204 $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $new_path);
205 }
206
213 public function handInTestBestSolution($html_string, $pdf_path)
214 {
216
217 $best_solution_path = $this->getTestArchive() . self::DIR_SEP . self::TEST_BEST_SOLUTION_PATH_COMPONENT;
218 if (!is_dir($best_solution_path)) {
219 mkdir($best_solution_path, 0777, true);
220 }
221
222 file_put_contents($best_solution_path . self::DIR_SEP . self::HTML_BEST_SOLUTION_FILENAME, $html_string);
223
224 copy($pdf_path, $best_solution_path . self::DIR_SEP . self::PDF_BEST_SOLUTION_FILENAME);
225
226 $this->logArchivingProcess(
227 date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING
228 . $best_solution_path . self::DIR_SEP . self::HTML_BEST_SOLUTION_FILENAME
229 );
230
231 $this->logArchivingProcess(
232 date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING
233 . $best_solution_path . self::DIR_SEP . self::PDF_BEST_SOLUTION_FILENAME
234 );
235 }
236
244 public function handInBestSolutionQuestionMaterial($question_fi, $orginial_filename, $file_path)
245 {
247
248 $best_solution_path = $this->getTestArchive() . self::DIR_SEP . self::TEST_BEST_SOLUTION_PATH_COMPONENT;
249 if (!is_dir($best_solution_path)) {
250 mkdir($best_solution_path, 0777, true);
251 }
252
253 $materials_path = $best_solution_path . self::DIR_SEP . self::TEST_MATERIALS_PATH_COMPONENT;
254 if (!is_dir($materials_path)) {
255 mkdir($materials_path, 0777, true);
256 }
257
258 $question_materials_path = $materials_path . self::DIR_SEP . self::QUESTION_PATH_COMPONENT_PREFIX . $question_fi;
259 if (!is_dir($question_materials_path)) {
260 mkdir($question_materials_path, 0777, true);
261 }
262
263 copy($file_path, $question_materials_path . self::DIR_SEP . $orginial_filename);
264
265 $this->logArchivingProcess(
266 date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING
267 . $question_materials_path . self::DIR_SEP . $orginial_filename
268 );
269 }
270
280 public function handInTestResult($active_fi, $pass, $pdf_path)
281 {
283 $this->ensurePassDataDirectoryIsAvailable($active_fi, $pass);
284 $new_path = $this->getPassDataDirectory($active_fi, $pass) . self::DIR_SEP
285 . self::TEST_RESULT_FILENAME . ($this->countFilesInDirectory($this->getPassDataDirectory($active_fi, $pass), self::TEST_RESULT_FILENAME))
286 . self::TEST_RESULT_POSTFIX;
287 copy($pdf_path, $new_path);
288 $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $new_path);
289 }
290
297 public function handInTestResultsOverview($html_string, $pdf_path)
298 {
300 $new_pdf_path = $this->getTestArchive() . self::DIR_SEP
301 . self::TEST_OVERVIEW_PDF_FILENAME
302 . $this->countFilesInDirectory($this->getTestArchive(), self::TEST_OVERVIEW_PDF_FILENAME) . self::TEST_OVERVIEW_PDF_POSTFIX;
303 copy($pdf_path, $new_pdf_path);
304 $html_path = $this->getTestArchive() . self::DIR_SEP . self::TEST_OVERVIEW_HTML_FILENAME
305 . $this->countFilesInDirectory($this->getTestArchive(), self::TEST_OVERVIEW_HTML_FILENAME) . self::TEST_OVERVIEW_HTML_POSTFIX;
306 file_put_contents($html_path, $html_string);
307
308 $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $new_pdf_path);
309 $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $html_path);
310 }
311
312 #endregion
313
314 #region TestArchive
315 // The TestArchive lives here: <external directory>/<client>/tst_data/archive/tst_<obj_id>/
316
322 protected function hasTestArchive()
323 {
324 return is_dir($this->getTestArchive());
325 }
326
330 protected function createArchiveForTest()
331 {
333 //mkdir( $this->getTestArchive(), 0777, true );
334 }
335
341 protected function getTestArchive()
342 {
343 $test_archive_directory = $this->external_directory_path . self::DIR_SEP . $this->client_id . self::DIR_SEP . 'tst_data'
344 . self::DIR_SEP . 'archive' . self::DIR_SEP . 'tst_' . $this->test_obj_id;
345 return $test_archive_directory;
346 }
347
355 protected function ensureTestArchiveIsAvailable()
356 {
357 if (!$this->hasTestArchive()) {
358 $this->createArchiveForTest();
359 }
360 return;
361 }
362
368 public function updateTestArchive()
369 {
370 $query = 'SELECT * FROM ass_log WHERE obj_fi = ' . $this->ilDB->quote($this->test_obj_id, 'integer');
371 $result = $this->ilDB->query($query);
372
373 $outfile_lines = '';
375 while ($row = $this->ilDB->fetchAssoc($result)) {
376 $outfile_lines .= "\r\n" . implode("\t", $row);
377 }
378 file_put_contents($this->getTestArchive() . self::DIR_SEP . self::TEST_LOG_FILENAME, $outfile_lines);
379
380 // Generate test pass overview
381 $test = new ilObjTest($this->test_obj_id, false);
382 $gui = new ilObjTestGUI();
383 $gui->object = $test;
384 $array_of_actives = array();
385 $participants = $test->getParticipants();
386
387 foreach ($participants as $key => $value) {
388 $array_of_actives[] = $key;
389 }
390 $output_template = $gui->createUserResults(true, false, true, $array_of_actives);
391
392 require_once 'class.ilTestPDFGenerator.php';
393 $filename = realpath($this->getTestArchive()) . self::DIR_SEP . 'participant_pass_overview.pdf';
395
396 return;
397 }
398
400 {
401 if (!$this->hasZipExportDirectory()) {
403 }
404 }
405
411 public function hasZipExportDirectory()
412 {
413 return is_dir($this->getZipExportDirectory());
414 }
415
416 protected function createZipExportDirectory()
417 {
418 mkdir($this->getZipExportDirectory(), 0777, true);
419 }
420
426 public function getZipExportDirectory()
427 {
428 return $this->external_directory_path . self::DIR_SEP . $this->client_id . self::DIR_SEP . 'tst_data'
429 . self::DIR_SEP . self::EXPORT_DIRECTORY . self::DIR_SEP . 'tst_' . $this->test_obj_id;
430 }
431
437 public function compressTestArchive()
438 {
439 $this->updateTestArchive();
441
442 $zip_output_path = $this->getZipExportDirectory();
443 $zip_output_filename = 'test_archive_obj_' . $this->test_obj_id . '_' . time() . '_.zip';
444
445 ilUtil::zip($this->getTestArchive(), $zip_output_path . self::DIR_SEP . $zip_output_filename, true);
446 return;
447 }
448
449 #endregion
450
451 #region PassDataDirectory
452 // The pass data directory contains all data relevant for a participants pass.
453 // In addition to the test-archive-directory, this directory lives here:
454 // .../<year>/<month>/<day>/<ActiveFi>_<Pass>[_<Lastname>][_<Firstname>][_<Matriculation>]/
455 // Lastname, Firstname and Matriculation are not mandatory in the directory name.
456
465 protected function hasPassDataDirectory($active_fi, $pass)
466 {
467 $pass_data_dir = $this->getPassDataDirectory($active_fi, $pass);
468 return is_dir($this->getPassDataDirectory($active_fi, $pass));
469 }
470
479 protected function createPassDataDirectory($active_fi, $pass)
480 {
481 mkdir($this->getPassDataDirectory($active_fi, $pass), 0777, true);
482 return;
483 }
484
485 private function buildPassDataDirectory($active_fi, $pass)
486 {
487 foreach ($this->archive_data_index as $data_index_entry) {
488 if ($data_index_entry != null && $data_index_entry['identifier'] == $active_fi . '|' . $pass) {
489 array_shift($data_index_entry);
490 return $this->getTestArchive() . self::DIR_SEP . implode(self::DIR_SEP, $data_index_entry);
491 }
492 }
493
494 return null;
495 }
496
505 protected function getPassDataDirectory($active_fi, $pass)
506 {
507 $passDataDir = $this->buildPassDataDirectory($active_fi, $pass);
508
509 if (!$passDataDir) {
510 if ($this->getParticipantData()) {
511 $usrData = $this->getParticipantData()->getUserDataByActiveId($active_fi);
512 $user = new ilObjUser();
513 $user->setFirstname($usrData['firstname']);
514 $user->setLastname($usrData['lastname']);
515 $user->setMatriculation($usrData['matriculation']);
516 $user->setFirstname($usrData['firstname']);
517 } else {
518 global $ilUser;
519 $user = $ilUser;
520 }
521
523 date(DATE_ISO8601),
524 $active_fi,
525 $pass,
526 $user->getFirstname(),
527 $user->getLastname(),
528 $user->getMatriculation()
529 );
530
531 $passDataDir = $this->buildPassDataDirectory($active_fi, $pass);
532 }
533
534 return $passDataDir;
535 }
536
547 protected function ensurePassDataDirectoryIsAvailable($active_fi, $pass)
548 {
549 if (!$this->hasPassDataDirectory($active_fi, $pass)) {
550 $this->createPassDataDirectory($active_fi, $pass);
551 }
552 return;
553 }
554
555 #endregion
556
557 #region PassMaterialsDirectory
558
567 protected function hasPassMaterialsDirectory($active_fi, $pass)
568 {
570 if (@is_dir($this->getPassMaterialsDirectory($active_fi, $pass))) {
571 return true;
572 }
573 return false;
574 }
575
584 protected function createPassMaterialsDirectory($active_fi, $pass)
585 {
586 // Data are taken from the current user as the implementation expects the first interaction of the pass
587 // takes place from the usage/behaviour of the current user.
588
589 if ($this->getParticipantData()) {
590 $usrData = $this->getParticipantData()->getUserDataByActiveId($active_fi);
591 $user = new ilObjUser();
592 $user->setFirstname($usrData['firstname']);
593 $user->setLastname($usrData['lastname']);
594 $user->setMatriculation($usrData['matriculation']);
595 $user->setFirstname($usrData['firstname']);
596 } else {
597 global $ilUser;
598 $user = $ilUser;
599 }
600
602 date('Y'),
603 $active_fi,
604 $pass,
605 $user->getFirstname(),
606 $user->getLastname(),
607 $user->getMatriculation()
608 );
609 mkdir($this->getPassMaterialsDirectory($active_fi, $pass), 0777, true);
610 }
611
620 protected function getPassMaterialsDirectory($active_fi, $pass)
621 {
622 $pass_data_directory = $this->getPassMaterialsDirectory($active_fi, $pass);
623 return $pass_data_directory . self::DIR_SEP . self::PASS_MATERIALS_PATH_COMPONENT;
624 }
625
635 protected function ensurePassMaterialsDirectoryIsAvailable($active_fi, $pass)
636 {
637 if (!$this->hasPassMaterialsDirectory($active_fi, $pass)) {
638 $this->createPassMaterialsDirectory($active_fi, $pass);
639 }
640 }
641
642 #endregion
643
649 protected function readArchiveDataIndex()
650 {
655 $data_index_file = $this->getTestArchive() . self::DIR_SEP . self::DATA_INDEX_FILENAME;
656
657 $contents = array();
658
660 if (@file_exists($data_index_file)) {
661 $lines = explode("\n", file_get_contents($data_index_file));
662 foreach ($lines as $line) {
663 $line_items = explode('|', $line);
664 $line_data['identifier'] = $line_items[0] . '|' . $line_items[1];
665 $line_data['yyyy'] = $line_items[2];
666 $line_data['mm'] = $line_items[3];
667 $line_data['dd'] = $line_items[4];
668 $line_data['directory'] = $line_items[5];
669 $contents[] = $line_data;
670 }
671 }
672 return $contents;
673 }
674
687 protected function appendToArchiveDataIndex($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation)
688 {
689 $line = $this->determinePassDataPath($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation);
690
691 $this->archive_data_index[] = $line;
692 $output_contents = '';
693
694 foreach ($this->archive_data_index as $line_data) {
695 if ($line_data['identifier'] == "|") {
696 continue;
697 }
698 $output_contents .= implode('|', $line_data) . "\n";
699 }
700
701 file_put_contents($this->getTestArchive() . self::DIR_SEP . self::DATA_INDEX_FILENAME, $output_contents);
702 $this->readArchiveDataIndex();
703 return;
704 }
705
718 protected function determinePassDataPath($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation)
719 {
720 $date = date_create_from_format(DATE_ISO8601, $date);
721 $line = array(
722 'identifier' => $active_fi . '|' . $pass,
723 'yyyy' => date_format($date, 'Y'),
724 'mm' => date_format($date, 'm'),
725 'dd' => date_format($date, 'd'),
726 'directory' => $active_fi . '_' . $pass . '_' . $user_firstname . '_' . $user_lastname . '_' . $matriculation
727 );
728 return $line;
729 }
730
738 protected function logArchivingProcess($message)
739 {
740 $archive = $this->getTestArchive() . self::DIR_SEP . self::ARCHIVE_LOG;
741 if (file_exists($archive)) {
742 $content = file_get_contents($archive) . "\n" . $message;
743 } else {
744 $content = $message;
745 }
746
747 file_put_contents($archive, $content);
748 }
749
758 protected function countFilesInDirectory($directory, $pattern = null)
759 {
760 $filecount = 0;
761
763 if ($handle = opendir($directory)) {
764 while (($file = readdir($handle)) !== false) {
765 if (!in_array($file, array( '.', '..' )) && !is_dir($directory . $file)) {
766 if ($pattern && strpos($file, $pattern) === 0) {
767 $filecount++;
768 }
769 }
770 }
771 }
772 return $filecount;
773 }
774}
date( 'd-M-Y', $objPHPExcel->getProperties() ->getCreated())
$result
$test
Definition: Utf8Test.php:84
An exception for terminatinating execution or to throw for unit testing.
Database Wrapper.
Definition: class.ilDB.php:30
query($sql, $a_handle_error=true)
Query.
fetchAssoc($a_set)
Fetch row as associative array from result set.
quote($a_query, $a_type=null)
Wrapper for quote method.
Class ilObjTestGUI.
Class ilTestArchiver.
handInTestResult($active_fi, $pass, $pdf_path)
Hands in an individual test result for a pass.
handInParticipantSubmission($active_fi, $pass, $pdf_path, $html_string)
Hands in a participants test submission ("a completed test") for archiving.
handInBestSolutionQuestionMaterial($question_fi, $orginial_filename, $file_path)
Hands in a file related to a question in context of the best solution.
compressTestArchive()
Generate the test archive for download.
hasPassMaterialsDirectory($active_fi, $pass)
Returns if the pass materials directory exists for a given pass.
countFilesInDirectory($directory, $pattern=null)
Returns the count of files in a directory, eventually matching the given, optional,...
ensureTestArchiveIsAvailable()
Ensures the availability of the test archive directory.
getTestArchive()
Returns the (theoretical) path to the archive directory of the test, this object is created for.
getZipExportDirectory()
Return the export directory, where zips are placed.
handInParticipantQuestionMaterial($active_fi, $pass, $question_fi, $original_filename, $file_path)
Hands in a particpants question material, such as an upload or other binary content.
getPassMaterialsDirectory($active_fi, $pass)
Returns the pass materials directory.
handInTestResultsOverview($html_string, $pdf_path)
Hands in a test results overview.
handInTestBestSolution($html_string, $pdf_path)
Hands in the best solution for a test.
hasPassDataDirectory($active_fi, $pass)
Checks if the directory for pass data is available.
setParticipantData($participantData)
readArchiveDataIndex()
Reads the archive data index.
getPassDataDirectory($active_fi, $pass)
Returns the pass data directory.
hasTestArchive()
Returns if the archive directory structure for the test the object is created for exists.
handInParticipantMisc($active_fi, $pass, $original_filename, $file_path)
Hands in a participants file, which is relevant for archiving but an unspecified type.
createPassDataDirectory($active_fi, $pass)
Creates pass data directory.
const TEST_BEST_SOLUTION_PATH_COMPONENT
buildPassDataDirectory($active_fi, $pass)
appendToArchiveDataIndex($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation)
Appends a line to the archive data index.
createPassMaterialsDirectory($active_fi, $pass)
Creates pass materials directory.
ensurePassMaterialsDirectoryIsAvailable($active_fi, $pass)
Ensures the availability of the pass materials directory.
const QUESTION_PATH_COMPONENT_PREFIX
updateTestArchive()
Replaces the test-log with the current one.
logArchivingProcess($message)
Logs to the archive log.
hasZipExportDirectory()
Returns if the export directory for zips exists.
createArchiveForTest()
Creates the directory for the test archive.
ensurePassDataDirectoryIsAvailable($active_fi, $pass)
Ensures the availability of the participant data directory.
determinePassDataPath($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation)
Determines the pass data path.
static generatePDF($pdf_output, $output_mode, $filename=null, $purpose=null)
static zip($a_dir, $a_file, $compress_content=false)
zips given directory/file into given zip.file
static makeDirParents($a_dir)
Create a new directory and all parent directories.
$key
Definition: croninfo.php:18
const PDF_USER_RESULT
PDF Purposes.
catch(Exception $e) $message
$query
if(!file_exists("$old.txt")) if( $old===$new) if(file_exists("$new.txt")) $file
$ilUser
Definition: imgupload.php:18