ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
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';
35  const TEST_OVERVIEW_PDF_POSTFIX = '.pdf';
36 
37  const TEST_OVERVIEW_HTML_FILENAME = 'results_overview_pdf_v';
38  const TEST_OVERVIEW_HTML_POSTFIX = '.html';
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;
83  protected $archive_data_index;
85  protected $ilDB;
90  protected $participantData;
91 
92  #endregion
93 
99  public function __construct($test_obj_id)
100  {
102  global $DIC;
103  $ilias = $DIC['ilias'];
104  $this->external_directory_path = $ilias->ini_ilias->readVariable('clients', 'datadir');
105  $this->client_id = $ilias->client_id;
106  $this->test_obj_id = $test_obj_id;
107  $this->ilDB = $ilias->db;
108 
109  $this->archive_data_index = $this->readArchiveDataIndex();
110 
111  $this->participantData = null;
112  }
113 
117  public function getParticipantData()
118  {
119  return $this->participantData;
120  }
121 
126  {
127  $this->participantData = $participantData;
128  }
129 
130  #region API methods
131 
144  public function handInParticipantSubmission($active_fi, $pass, $pdf_path, $html_string)
145  {
147  $this->ensurePassDataDirectoryIsAvailable($active_fi, $pass);
148 
149  $pdf_new_path = $this->getPassDataDirectory($active_fi, $pass) . self::DIR_SEP
150  . self::PDF_SUBMISSION_FILENAME;
151  copy($pdf_path, $pdf_new_path);
152  # /home/mbecker/public_html/ilias/trunk-primary/extern/default/tst_data/archive/tst_350/2013/09/19/80_1_root_user_/test_submission.pdf
153  $html_new_path = $this->getPassDataDirectory($active_fi, $pass) . self::DIR_SEP
154  . self::HTML_SUBMISSION_FILENAME;
155  file_put_contents($html_new_path, $html_string);
156 
157  $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $pdf_new_path);
158  $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $html_new_path);
159  }
160 
170  public function handInParticipantQuestionMaterial($active_fi, $pass, $question_fi, $original_filename, $file_path)
171  {
173  $this->ensurePassDataDirectoryIsAvailable($active_fi, $pass);
174 
175  $pass_question_directory = $this->getPassDataDirectory($active_fi, $pass)
176  . self::DIR_SEP . self::QUESTION_PATH_COMPONENT_PREFIX . $question_fi;
177  if (!is_dir($pass_question_directory)) {
178  mkdir($pass_question_directory, 0777, true);
179  }
180 
181  copy($file_path, $pass_question_directory . self::DIR_SEP . $original_filename);
182 
183  $this->logArchivingProcess(
184  date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING
185  . $pass_question_directory . self::DIR_SEP . $original_filename
186  );
187  }
188 
199  public function handInParticipantMisc($active_fi, $pass, $original_filename, $file_path)
200  {
202  $this->ensurePassDataDirectoryIsAvailable($active_fi, $pass);
203  $new_path = $this->getPassDataDirectory($active_fi, $pass) . self::DIR_SEP . $original_filename;
204  copy($file_path, $new_path);
205  $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $new_path);
206  }
207 
214  public function handInTestBestSolution($html_string, $pdf_path)
215  {
217 
218  $best_solution_path = $this->getTestArchive() . self::DIR_SEP . self::TEST_BEST_SOLUTION_PATH_COMPONENT;
219  if (!is_dir($best_solution_path)) {
220  mkdir($best_solution_path, 0777, true);
221  }
222 
223  file_put_contents($best_solution_path . self::DIR_SEP . self::HTML_BEST_SOLUTION_FILENAME, $html_string);
224 
225  copy($pdf_path, $best_solution_path . self::DIR_SEP . self::PDF_BEST_SOLUTION_FILENAME);
226 
227  $this->logArchivingProcess(
228  date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING
229  . $best_solution_path . self::DIR_SEP . self::HTML_BEST_SOLUTION_FILENAME
230  );
231 
232  $this->logArchivingProcess(
233  date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING
234  . $best_solution_path . self::DIR_SEP . self::PDF_BEST_SOLUTION_FILENAME
235  );
236  }
237 
245  public function handInBestSolutionQuestionMaterial($question_fi, $orginial_filename, $file_path)
246  {
248 
249  $best_solution_path = $this->getTestArchive() . self::DIR_SEP . self::TEST_BEST_SOLUTION_PATH_COMPONENT;
250  if (!is_dir($best_solution_path)) {
251  mkdir($best_solution_path, 0777, true);
252  }
253 
254  $materials_path = $best_solution_path . self::DIR_SEP . self::TEST_MATERIALS_PATH_COMPONENT;
255  if (!is_dir($materials_path)) {
256  mkdir($materials_path, 0777, true);
257  }
258 
259  $question_materials_path = $materials_path . self::DIR_SEP . self::QUESTION_PATH_COMPONENT_PREFIX . $question_fi;
260  if (!is_dir($question_materials_path)) {
261  mkdir($question_materials_path, 0777, true);
262  }
263 
264  copy($file_path, $question_materials_path . self::DIR_SEP . $orginial_filename);
265 
266  $this->logArchivingProcess(
267  date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING
268  . $question_materials_path . self::DIR_SEP . $orginial_filename
269  );
270  }
271 
281  public function handInTestResult($active_fi, $pass, $pdf_path)
282  {
284  $this->ensurePassDataDirectoryIsAvailable($active_fi, $pass);
285  $new_path = $this->getPassDataDirectory($active_fi, $pass) . self::DIR_SEP
286  . self::TEST_RESULT_FILENAME . ($this->countFilesInDirectory($this->getPassDataDirectory($active_fi, $pass), self::TEST_RESULT_FILENAME))
287  . self::TEST_RESULT_POSTFIX;
288  copy($pdf_path, $new_path);
289  $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $new_path);
290  }
291 
298  public function handInTestResultsOverview($html_string, $pdf_path)
299  {
301  $new_pdf_path = $this->getTestArchive() . self::DIR_SEP
302  . self::TEST_OVERVIEW_PDF_FILENAME
303  . $this->countFilesInDirectory($this->getTestArchive(), self::TEST_OVERVIEW_PDF_FILENAME) . self::TEST_OVERVIEW_PDF_POSTFIX;
304  copy($pdf_path, $new_pdf_path);
305  $html_path = $this->getTestArchive() . self::DIR_SEP . self::TEST_OVERVIEW_HTML_FILENAME
306  . $this->countFilesInDirectory($this->getTestArchive(), self::TEST_OVERVIEW_HTML_FILENAME) . self::TEST_OVERVIEW_HTML_POSTFIX;
307  file_put_contents($html_path, $html_string);
308 
309  $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $new_pdf_path);
310  $this->logArchivingProcess(date(self::LOG_DTSGROUP_FORMAT) . self::LOG_ADDITION_STRING . $html_path);
311  }
312 
313  #endregion
314 
315  #region TestArchive
316  // The TestArchive lives here: <external directory>/<client>/tst_data/archive/tst_<obj_id>/
317 
323  protected function hasTestArchive()
324  {
325  return is_dir($this->getTestArchive());
326  }
327 
331  protected function createArchiveForTest()
332  {
334  //mkdir( $this->getTestArchive(), 0777, true );
335  }
336 
342  protected function getTestArchive()
343  {
344  $test_archive_directory = $this->external_directory_path . self::DIR_SEP . $this->client_id . self::DIR_SEP . 'tst_data'
345  . self::DIR_SEP . 'archive' . self::DIR_SEP . 'tst_' . $this->test_obj_id;
346  return $test_archive_directory;
347  }
348 
356  protected function ensureTestArchiveIsAvailable()
357  {
358  if (!$this->hasTestArchive()) {
359  $this->createArchiveForTest();
360  }
361  return;
362  }
363 
369  public function updateTestArchive()
370  {
371  $query = 'SELECT * FROM ass_log WHERE obj_fi = ' . $this->ilDB->quote($this->test_obj_id, 'integer');
372  $result = $this->ilDB->query($query);
373 
374  $outfile_lines = '';
376  while ($row = $this->ilDB->fetchAssoc($result)) {
377  $outfile_lines .= "\r\n" . implode("\t", $row);
378  }
379  file_put_contents($this->getTestArchive() . self::DIR_SEP . self::TEST_LOG_FILENAME, $outfile_lines);
380 
381  // Generate test pass overview
382  $test = new ilObjTest($this->test_obj_id, false);
383  require_once 'Modules/Test/classes/class.ilParticipantsTestResultsGUI.php';
384  $gui = new ilParticipantsTestResultsGUI();
385  $gui->setTestObj($test);
386  require_once 'Modules/Test/classes/class.ilTestObjectiveOrientedContainer.php';
387  $objectiveOrientedContainer = new ilTestObjectiveOrientedContainer();
388  $gui->setObjectiveParent($objectiveOrientedContainer);
389  $array_of_actives = array();
390  $participants = $test->getParticipants();
391 
392  foreach ($participants as $key => $value) {
393  $array_of_actives[] = $key;
394  }
395  $output_template = $gui->createUserResults(true, false, true, $array_of_actives);
396 
397  require_once 'Modules/Test/classes/class.ilTestPDFGenerator.php';
398  $filename = realpath($this->getTestArchive()) . self::DIR_SEP . 'participant_pass_overview.pdf';
400 
401  return;
402  }
403 
405  {
406  if (!$this->hasZipExportDirectory()) {
407  $this->createZipExportDirectory();
408  }
409  }
410 
416  public function hasZipExportDirectory()
417  {
418  return is_dir($this->getZipExportDirectory());
419  }
420 
421  protected function createZipExportDirectory()
422  {
423  mkdir($this->getZipExportDirectory(), 0777, true);
424  }
425 
431  public function getZipExportDirectory()
432  {
433  return $this->external_directory_path . self::DIR_SEP . $this->client_id . self::DIR_SEP . 'tst_data'
434  . self::DIR_SEP . self::EXPORT_DIRECTORY . self::DIR_SEP . 'tst_' . $this->test_obj_id;
435  }
436 
442  public function compressTestArchive()
443  {
444  $this->updateTestArchive();
446 
447  $zip_output_path = $this->getZipExportDirectory();
448  $zip_output_filename = 'test_archive_obj_' . $this->test_obj_id . '_' . time() . '_.zip';
449 
450  ilUtil::zip($this->getTestArchive(), $zip_output_path . self::DIR_SEP . $zip_output_filename, true);
451  return;
452  }
453 
454  #endregion
455 
456  #region PassDataDirectory
457  // The pass data directory contains all data relevant for a participants pass.
458  // In addition to the test-archive-directory, this directory lives here:
459  // .../<year>/<month>/<day>/<ActiveFi>_<Pass>[_<Lastname>][_<Firstname>][_<Matriculation>]/
460  // Lastname, Firstname and Matriculation are not mandatory in the directory name.
461 
470  protected function hasPassDataDirectory($active_fi, $pass)
471  {
472  $pass_data_dir = $this->getPassDataDirectory($active_fi, $pass);
473  return is_dir($this->getPassDataDirectory($active_fi, $pass));
474  }
475 
484  protected function createPassDataDirectory($active_fi, $pass)
485  {
486  mkdir($this->getPassDataDirectory($active_fi, $pass), 0777, true);
487  return;
488  }
489 
490  private function buildPassDataDirectory($active_fi, $pass)
491  {
492  foreach ($this->archive_data_index as $data_index_entry) {
493  if ($data_index_entry != null && $data_index_entry['identifier'] == $active_fi . '|' . $pass) {
494  array_shift($data_index_entry);
495  return $this->getTestArchive() . self::DIR_SEP . implode(self::DIR_SEP, $data_index_entry);
496  }
497  }
498 
499  return null;
500  }
501 
510  protected function getPassDataDirectory($active_fi, $pass)
511  {
512  $passDataDir = $this->buildPassDataDirectory($active_fi, $pass);
513 
514  if (!$passDataDir) {
515  if ($this->getParticipantData()) {
516  $usrData = $this->getParticipantData()->getUserDataByActiveId($active_fi);
517  $user = new ilObjUser();
518  $user->setFirstname($usrData['firstname']);
519  $user->setLastname($usrData['lastname']);
520  $user->setMatriculation($usrData['matriculation']);
521  $user->setFirstname($usrData['firstname']);
522  } else {
523  global $DIC;
524  $ilUser = $DIC['ilUser'];
525  $user = $ilUser;
526  }
527 
529  date(DATE_ISO8601),
530  $active_fi,
531  $pass,
532  $user->getFirstname(),
533  $user->getLastname(),
534  $user->getMatriculation()
535  );
536 
537  $passDataDir = $this->buildPassDataDirectory($active_fi, $pass);
538  }
539 
540  return $passDataDir;
541  }
542 
553  protected function ensurePassDataDirectoryIsAvailable($active_fi, $pass)
554  {
555  if (!$this->hasPassDataDirectory($active_fi, $pass)) {
556  $this->createPassDataDirectory($active_fi, $pass);
557  }
558  return;
559  }
560 
561  #endregion
562 
563  #region PassMaterialsDirectory
564 
573  protected function hasPassMaterialsDirectory($active_fi, $pass)
574  {
576  if (@is_dir($this->getPassMaterialsDirectory($active_fi, $pass))) {
577  return true;
578  }
579  return false;
580  }
581 
590  protected function createPassMaterialsDirectory($active_fi, $pass)
591  {
592  // Data are taken from the current user as the implementation expects the first interaction of the pass
593  // takes place from the usage/behaviour of the current user.
594 
595  if ($this->getParticipantData()) {
596  $usrData = $this->getParticipantData()->getUserDataByActiveId($active_fi);
597  $user = new ilObjUser();
598  $user->setFirstname($usrData['firstname']);
599  $user->setLastname($usrData['lastname']);
600  $user->setMatriculation($usrData['matriculation']);
601  $user->setFirstname($usrData['firstname']);
602  } else {
603  global $DIC;
604  $ilUser = $DIC['ilUser'];
605  $user = $ilUser;
606  }
607 
609  date('Y'),
610  $active_fi,
611  $pass,
612  $user->getFirstname(),
613  $user->getLastname(),
614  $user->getMatriculation()
615  );
616  mkdir($this->getPassMaterialsDirectory($active_fi, $pass), 0777, true);
617  }
618 
627  protected function getPassMaterialsDirectory($active_fi, $pass)
628  {
629  $pass_data_directory = $this->getPassMaterialsDirectory($active_fi, $pass);
630  return $pass_data_directory . self::DIR_SEP . self::PASS_MATERIALS_PATH_COMPONENT;
631  }
632 
642  protected function ensurePassMaterialsDirectoryIsAvailable($active_fi, $pass)
643  {
644  if (!$this->hasPassMaterialsDirectory($active_fi, $pass)) {
645  $this->createPassMaterialsDirectory($active_fi, $pass);
646  }
647  }
648 
649  #endregion
650 
656  protected function readArchiveDataIndex()
657  {
662  $data_index_file = $this->getTestArchive() . self::DIR_SEP . self::DATA_INDEX_FILENAME;
663 
664  $contents = array();
665 
667  if (@file_exists($data_index_file)) {
668  $lines = explode("\n", file_get_contents($data_index_file));
669  foreach ($lines as $line) {
670  $line_items = explode('|', $line);
671  $line_data['identifier'] = $line_items[0] . '|' . $line_items[1];
672  $line_data['yyyy'] = $line_items[2];
673  $line_data['mm'] = $line_items[3];
674  $line_data['dd'] = $line_items[4];
675  $line_data['directory'] = $line_items[5];
676  $contents[] = $line_data;
677  }
678  }
679  return $contents;
680  }
681 
694  protected function appendToArchiveDataIndex($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation)
695  {
696  $line = $this->determinePassDataPath($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation);
697 
698  $this->archive_data_index[] = $line;
699  $output_contents = '';
700 
701  foreach ($this->archive_data_index as $line_data) {
702  if ($line_data['identifier'] == "|") {
703  continue;
704  }
705  $output_contents .= implode('|', $line_data) . "\n";
706  }
707 
708  file_put_contents($this->getTestArchive() . self::DIR_SEP . self::DATA_INDEX_FILENAME, $output_contents);
709  $this->readArchiveDataIndex();
710  return;
711  }
712 
725  protected function determinePassDataPath($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation)
726  {
727  $date = date_create_from_format(DATE_ISO8601, $date);
728  $line = array(
729  'identifier' => $active_fi . '|' . $pass,
730  'yyyy' => date_format($date, 'Y'),
731  'mm' => date_format($date, 'm'),
732  'dd' => date_format($date, 'd'),
733  'directory' => $active_fi . '_' . $pass . '_' . $user_firstname . '_' . $user_lastname . '_' . $matriculation
734  );
735  return $line;
736  }
737 
745  protected function logArchivingProcess($message)
746  {
747  $archive = $this->getTestArchive() . self::DIR_SEP . self::ARCHIVE_LOG;
748  if (file_exists($archive)) {
749  $content = file_get_contents($archive) . "\n" . $message;
750  } else {
751  $content = $message;
752  }
753 
754  file_put_contents($archive, $content);
755  }
756 
765  protected function countFilesInDirectory($directory, $pattern = null)
766  {
767  $filecount = 0;
768 
770  if ($handle = opendir($directory)) {
771  while (($file = readdir($handle)) !== false) {
772  if (!in_array($file, array( '.', '..' )) && !is_dir($directory . $file)) {
773  if ($pattern && strpos($file, $pattern) === 0) {
774  $filecount++;
775  }
776  }
777  }
778  }
779  return $filecount;
780  }
781 }
getPassDataDirectory($active_fi, $pass)
Returns the pass data directory.
static makeDirParents($a_dir)
Create a new directory and all parent directories.
determinePassDataPath($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation)
Determines the pass data path.
createArchiveForTest()
Creates the directory for the test archive.
$result
handInTestResultsOverview($html_string, $pdf_path)
Hands in a test results overview.
global $DIC
Definition: saml.php:7
createPassDataDirectory($active_fi, $pass)
Creates pass data directory.
countFilesInDirectory($directory, $pattern=null)
Returns the count of files in a directory, eventually matching the given, optional, pattern.
const PDF_USER_RESULT
PDF Purposes.
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.
compressTestArchive()
Generate the test archive for download.
readArchiveDataIndex()
Reads the archive data index.
catch(Exception $e) $message
const QUESTION_PATH_COMPONENT_PREFIX
getZipExportDirectory()
Return the export directory, where zips are placed.
buildPassDataDirectory($active_fi, $pass)
appendToArchiveDataIndex($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation)
Appends a line to the archive data index.
handInParticipantSubmission($active_fi, $pass, $pdf_path, $html_string)
Hands in a participants test submission ("a completed test") for archiving.
ensurePassDataDirectoryIsAvailable($active_fi, $pass)
Ensures the availability of the participant data directory.
$ilUser
Definition: imgupload.php:18
handInBestSolutionQuestionMaterial($question_fi, $orginial_filename, $file_path)
Hands in a file related to a question in context of the best solution.
$query
hasZipExportDirectory()
Returns if the export directory for zips exists.
$user
Definition: migrateto20.php:57
static zip($a_dir, $a_file, $compress_content=false)
zips given directory/file into given zip.file
hasTestArchive()
Returns if the archive directory structure for the test the object is created for exists...
handInTestBestSolution($html_string, $pdf_path)
Hands in the best solution for a test.
$filename
Definition: buildRTE.php:89
$row
Class ilTestArchiver.
getTestArchive()
Returns the (theoretical) path to the archive directory of the test, this object is created for...
hasPassDataDirectory($active_fi, $pass)
Checks if the directory for pass data is available.
hasPassMaterialsDirectory($active_fi, $pass)
Returns if the pass materials directory exists for a given pass.
createPassMaterialsDirectory($active_fi, $pass)
Creates pass materials directory.
const TEST_BEST_SOLUTION_PATH_COMPONENT
ensurePassMaterialsDirectoryIsAvailable($active_fi, $pass)
Ensures the availability of the pass materials directory.
ensureTestArchiveIsAvailable()
Ensures the availability of the test archive directory.
static generatePDF($pdf_output, $output_mode, $filename=null, $purpose=null)
handInParticipantMisc($active_fi, $pass, $original_filename, $file_path)
Hands in a participants file, which is relevant for archiving but an unspecified type.
$key
Definition: croninfo.php:18
setParticipantData($participantData)
logArchivingProcess($message)
Logs to the archive log.
$test
Definition: Utf8Test.php:84
updateTestArchive()
Replaces the test-log with the current one.
handInTestResult($active_fi, $pass, $pdf_path)
Hands in an individual test result for a pass.