ILIAS  release_7 Revision v7.30-3-g800a261c036
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.pdf';
32
33 const TEST_OVERVIEW_PDF_FILENAME = 'results_overview_html_v';
35
36 const TEST_OVERVIEW_HTML_FILENAME = 'results_overview_pdf_v';
38
39 const LOG_DTSGROUP_FORMAT = 'D M j G:i:s T Y';
40 const LOG_ADDITION_STRING = ' Adding ';
41 const LOG_CREATION_STRING = ' Creating ';
42 const LOG_UPDATE_STRING = ' Updating ';
43 const LOG_DELETION_STRING = ' Deleting ';
44
45 const TEST_LOG_FILENAME = 'test.log';
46 const DATA_INDEX_FILENAME = 'data_index.csv';
47 const ARCHIVE_LOG = 'archive.log';
48
49 const EXPORT_DIRECTORY = 'archive_exports';
50
51 #endregion
52
53 /*
54 * Test-Archive Schema:
55 *
56 * <external directory>/<client>/tst_data/archive/tst_<obj_id>/
57 * - archive_data_index.dat
58 * - archive_log.log
59 *
60 * - test_log.log
61 *
62 * - test_results_v<n>.pdf
63 * - test_results_v<n>.csv
64 *
65 * -> best_solution/
66 * best_solution_v<n>.pdf
67 * -> /materials/q_<question_fi>/<n>_<filename>
68 *
69 * -> <year>/<month>/<day>/<ActiveFi>_<Pass>[_<Lastname>][_<Firstname>][_<Matriculation>]/
70 * -> test_submission.pdf
71 * -> test_submission.html
72 * -> test_submission.sig (et al)
73 * -> test_result_v<n>.pdf
74 * -> /materials_v<n>/<question_fi>/<n>_<filename>
75 */
76
77 #region Properties
78
80 protected $client_id;
81 protected $test_obj_id;
84 protected $ilDB;
90
91 #endregion
92
98 public function __construct($test_obj_id)
99 {
101 global $DIC;
102 $ilias = $DIC['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 . self::TEST_RESULT_FILENAME;
285 copy($pdf_path, $new_path);
286 $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $new_path);
287 }
288
295 public function handInTestResultsOverview($html_string, $pdf_path)
296 {
298 $new_pdf_path = $this->getTestArchive() . self::DIR_SEP
299 . self::TEST_OVERVIEW_PDF_FILENAME
300 . $this->countFilesInDirectory($this->getTestArchive(), self::TEST_OVERVIEW_PDF_FILENAME) . self::TEST_OVERVIEW_PDF_POSTFIX;
301 copy($pdf_path, $new_pdf_path);
302 $html_path = $this->getTestArchive() . self::DIR_SEP . self::TEST_OVERVIEW_HTML_FILENAME
303 . $this->countFilesInDirectory($this->getTestArchive(), self::TEST_OVERVIEW_HTML_FILENAME) . self::TEST_OVERVIEW_HTML_POSTFIX;
304 file_put_contents($html_path, $html_string);
305
306 $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $new_pdf_path);
307 $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $html_path);
308 }
309
310 #endregion
311
312 #region TestArchive
313 // The TestArchive lives here: <external directory>/<client>/tst_data/archive/tst_<obj_id>/
314
320 protected function hasTestArchive()
321 {
322 return is_dir($this->getTestArchive());
323 }
324
328 protected function createArchiveForTest()
329 {
331 //mkdir( $this->getTestArchive(), 0777, true );
332 }
333
339 protected function getTestArchive()
340 {
341 $test_archive_directory = $this->external_directory_path . self::DIR_SEP . $this->client_id . self::DIR_SEP . 'tst_data'
342 . self::DIR_SEP . 'archive' . self::DIR_SEP . 'tst_' . $this->test_obj_id;
343 return $test_archive_directory;
344 }
345
353 protected function ensureTestArchiveIsAvailable()
354 {
355 if (!$this->hasTestArchive()) {
356 $this->createArchiveForTest();
357 }
358 return;
359 }
360
366 public function updateTestArchive()
367 {
368 $query = 'SELECT * FROM ass_log WHERE obj_fi = ' . $this->ilDB->quote($this->test_obj_id, 'integer');
369 $result = $this->ilDB->query($query);
370
371 $outfile_lines = '';
373 while ($row = $this->ilDB->fetchAssoc($result)) {
374 $outfile_lines .= "\r\n" . implode("\t", $row);
375 }
376 file_put_contents($this->getTestArchive() . self::DIR_SEP . self::TEST_LOG_FILENAME, $outfile_lines);
377
378 // Generate test pass overview
379 $test = new ilObjTest($this->test_obj_id, false);
380 require_once 'Modules/Test/classes/class.ilParticipantsTestResultsGUI.php';
381 $gui = new ilParticipantsTestResultsGUI();
382 $gui->setTestObj($test);
383 require_once 'Modules/Test/classes/class.ilTestObjectiveOrientedContainer.php';
384 $objectiveOrientedContainer = new ilTestObjectiveOrientedContainer();
385 $gui->setObjectiveParent($objectiveOrientedContainer);
386 $array_of_actives = array();
387 $participants = $test->getParticipants();
388
389 foreach ($participants as $key => $value) {
390 $array_of_actives[] = $key;
391 }
392 $output_template = $gui->createUserResults(true, false, true, $array_of_actives);
393
394 $filename = realpath($this->getTestArchive()) . self::DIR_SEP . 'participant_pass_overview.pdf';
396
397 return;
398 }
399
401 {
402 if (!$this->hasZipExportDirectory()) {
404 }
405 }
406
412 public function hasZipExportDirectory()
413 {
414 return is_dir($this->getZipExportDirectory());
415 }
416
417 protected function createZipExportDirectory()
418 {
419 mkdir($this->getZipExportDirectory(), 0777, true);
420 }
421
427 public function getZipExportDirectory()
428 {
429 return $this->external_directory_path . self::DIR_SEP . $this->client_id . self::DIR_SEP . 'tst_data'
430 . self::DIR_SEP . self::EXPORT_DIRECTORY . self::DIR_SEP . 'tst_' . $this->test_obj_id;
431 }
432
438 public function compressTestArchive()
439 {
440 $this->updateTestArchive();
442
443 $zip_output_path = $this->getZipExportDirectory();
444 $zip_output_filename = 'test_archive_obj_' . $this->test_obj_id . '_' . time() . '_.zip';
445
446 ilUtil::zip($this->getTestArchive(), $zip_output_path . self::DIR_SEP . $zip_output_filename, true);
447 return;
448 }
449
450 #endregion
451
452 #region PassDataDirectory
453 // The pass data directory contains all data relevant for a participants pass.
454 // In addition to the test-archive-directory, this directory lives here:
455 // .../<year>/<month>/<day>/<ActiveFi>_<Pass>[_<Lastname>][_<Firstname>][_<Matriculation>]/
456 // Lastname, Firstname and Matriculation are not mandatory in the directory name.
457
466 protected function hasPassDataDirectory($active_fi, $pass)
467 {
468 $pass_data_dir = $this->getPassDataDirectory($active_fi, $pass);
469 return is_dir($this->getPassDataDirectory($active_fi, $pass));
470 }
471
480 protected function createPassDataDirectory($active_fi, $pass)
481 {
482 mkdir($this->getPassDataDirectory($active_fi, $pass), 0777, true);
483 return;
484 }
485
486 private function buildPassDataDirectory($active_fi, $pass)
487 {
488 foreach ($this->archive_data_index as $data_index_entry) {
489 if ($data_index_entry != null && $data_index_entry['identifier'] == $active_fi . '|' . $pass) {
490 array_shift($data_index_entry);
491 return $this->getTestArchive() . self::DIR_SEP . implode(self::DIR_SEP, $data_index_entry);
492 }
493 }
494
495 return null;
496 }
497
506 protected function getPassDataDirectory($active_fi, $pass)
507 {
508 $passDataDir = $this->buildPassDataDirectory($active_fi, $pass);
509
510 if (!$passDataDir) {
511 $test_obj = new ilObjTest($this->test_obj_id, false);
512 if ($test_obj->getAnonymity()) {
513 $firstname = 'anonym';
514 $lastname = '';
515 $matriculation = '0';
516 } else {
517 if ($this->getParticipantData()) {
518 $usrData = $this->getParticipantData()->getUserDataByActiveId($active_fi);
519 $firstname = $usrData['firstname'];
520 $lastname = $usrData['lastname'];
521 $matriculation = $usrData['matriculation'];
522 } else {
523 global $DIC;
524 $ilUser = $DIC['ilUser'];
525 $firstname = $ilUser->getFirstname();
526 $lastname = $ilUser->getLastname();
527 $matriculation = $ilUser->getMatriculation();
528 }
529 }
530
532 date(DATE_ISO8601),
533 $active_fi,
534 $pass,
535 $firstname,
536 $lastname,
537 $matriculation
538 );
539
540 $passDataDir = $this->buildPassDataDirectory($active_fi, $pass);
541 }
542
543 return $passDataDir;
544 }
545
556 protected function ensurePassDataDirectoryIsAvailable($active_fi, $pass)
557 {
558 if (!$this->hasPassDataDirectory($active_fi, $pass)) {
559 $this->createPassDataDirectory($active_fi, $pass);
560 }
561 return;
562 }
563
564 #endregion
565
566 #region PassMaterialsDirectory
567
576 protected function hasPassMaterialsDirectory($active_fi, $pass)
577 {
579 if (@is_dir($this->getPassMaterialsDirectory($active_fi, $pass))) {
580 return true;
581 }
582 return false;
583 }
584
593 protected function createPassMaterialsDirectory($active_fi, $pass)
594 {
595 // Data are taken from the current user as the implementation expects the first interaction of the pass
596 // takes place from the usage/behaviour of the current user.
597
598 if ($this->getParticipantData()) {
599 $usrData = $this->getParticipantData()->getUserDataByActiveId($active_fi);
600 $user = new ilObjUser();
601 $user->setFirstname($usrData['firstname']);
602 $user->setLastname($usrData['lastname']);
603 $user->setMatriculation($usrData['matriculation']);
604 $user->setFirstname($usrData['firstname']);
605 } else {
606 global $DIC;
607 $ilUser = $DIC['ilUser'];
608 $user = $ilUser;
609 }
610
612 date('Y'),
613 $active_fi,
614 $pass,
615 $user->getFirstname(),
616 $user->getLastname(),
617 $user->getMatriculation()
618 );
619 mkdir($this->getPassMaterialsDirectory($active_fi, $pass), 0777, true);
620 }
621
630 protected function getPassMaterialsDirectory($active_fi, $pass)
631 {
632 $pass_data_directory = $this->getPassMaterialsDirectory($active_fi, $pass);
633 return $pass_data_directory . self::DIR_SEP . self::PASS_MATERIALS_PATH_COMPONENT;
634 }
635
645 protected function ensurePassMaterialsDirectoryIsAvailable($active_fi, $pass)
646 {
647 if (!$this->hasPassMaterialsDirectory($active_fi, $pass)) {
648 $this->createPassMaterialsDirectory($active_fi, $pass);
649 }
650 }
651
652 #endregion
653
659 protected function readArchiveDataIndex()
660 {
665 $data_index_file = $this->getTestArchive() . self::DIR_SEP . self::DATA_INDEX_FILENAME;
666
667 $contents = array();
668
670 if (@file_exists($data_index_file)) {
671 $lines = explode("\n", file_get_contents($data_index_file));
672 foreach ($lines as $line) {
673 $line_items = explode('|', $line);
674 $line_data = [];
675 $line_data['identifier'] = $line_items[0] . '|' . $line_items[1];
676 $line_data['yyyy'] = $line_items[2];
677 $line_data['mm'] = $line_items[3];
678 $line_data['dd'] = $line_items[4];
679 $line_data['directory'] = $line_items[5];
680 $contents[] = $line_data;
681 }
682 }
683 return $contents;
684 }
685
698 protected function appendToArchiveDataIndex($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation)
699 {
700 $line = $this->determinePassDataPath($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation);
701
702 $this->archive_data_index[] = $line;
703 $output_contents = '';
704
705 foreach ($this->archive_data_index as $line_data) {
706 if ($line_data['identifier'] == "|") {
707 continue;
708 }
709 $output_contents .= implode('|', $line_data) . "\n";
710 }
711
712 file_put_contents($this->getTestArchive() . self::DIR_SEP . self::DATA_INDEX_FILENAME, $output_contents);
713 $this->readArchiveDataIndex();
714 return;
715 }
716
729 protected function determinePassDataPath($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation)
730 {
731 $date = date_create_from_format(DATE_ISO8601, $date);
732 $line = array(
733 'identifier' => $active_fi . '|' . $pass,
734 'yyyy' => date_format($date, 'Y'),
735 'mm' => date_format($date, 'm'),
736 'dd' => date_format($date, 'd'),
737 'directory' => $active_fi . '_' . $pass . '_' . $user_firstname . '_' . $user_lastname . '_' . $matriculation
738 );
739 return $line;
740 }
741
749 protected function logArchivingProcess($message)
750 {
751 $archive = $this->getTestArchive() . self::DIR_SEP . self::ARCHIVE_LOG;
752 if (file_exists($archive)) {
753 $content = file_get_contents($archive) . "\n" . $message;
754 } else {
755 $content = $message;
756 }
757
758 file_put_contents($archive, $content);
759 }
760
769 protected function countFilesInDirectory($directory, $pattern = null)
770 {
771 $filecount = 0;
772
774 if ($handle = opendir($directory)) {
775 while (($file = readdir($handle)) !== false) {
776 if (!in_array($file, array( '.', '..' )) && !is_dir($directory . $file)) {
777 if ($pattern && strpos($file, $pattern) === 0) {
778 $filecount++;
779 }
780 }
781 }
782 }
783 return $filecount;
784 }
785}
$result
$test
Definition: Utf8Test.php:84
$filename
Definition: buildRTE.php:89
An exception for terminatinating execution or to throw for unit testing.
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.
global $DIC
Definition: goto.php:24
$ilUser
Definition: imgupload.php:18
const PDF_USER_RESULT
PDF Purposes.
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
$query
$message
Definition: xapiexit.php:14