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';
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 $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  {
118  return $this->participantData;
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
149  . self::PDF_SUBMISSION_FILENAME;
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
153  . self::HTML_SUBMISSION_FILENAME;
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()) {
402  $this->createZipExportDirectory();
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 }
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.
Class ilObjTestGUI.
$result
handInTestResultsOverview($html_string, $pdf_path)
Hands in a test results overview.
query($sql, $a_handle_error=true)
Query.
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.
fetchAssoc($a_set)
Fetch row as associative array from result set.
quote($a_query, $a_type=null)
Wrapper for quote method.
catch(Exception $e) $message
const QUESTION_PATH_COMPONENT_PREFIX
getZipExportDirectory()
Return the export directory, where zips are placed.
buildPassDataDirectory($active_fi, $pass)
date( 'd-M-Y', $objPHPExcel->getProperties() ->getCreated())
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.
static zip($a_dir, $a_file, $compress_content=false)
zips given directory/file into given zip.file
Create styles array
The data for the language used.
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.
Class ilTestArchiver.
getTestArchive()
Returns the (theoretical) path to the archive directory of the test, this object is created for...
Database Wrapper.
Definition: class.ilDB.php:29
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.
Add data(end) time
Method that wraps PHPs time in order to allow simulations with the workflow.
if(!file_exists("$old.txt")) if($old===$new) if(file_exists("$new.txt")) $file
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.