ILIAS  release_6 Revision v6.24-5-g0c8bfefb3b8
All Data Structures Namespaces Files Functions Variables Modules Pages
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  $filename = realpath($this->getTestArchive()) . self::DIR_SEP . 'participant_pass_overview.pdf';
399 
400  return;
401  }
402 
404  {
405  if (!$this->hasZipExportDirectory()) {
406  $this->createZipExportDirectory();
407  }
408  }
409 
415  public function hasZipExportDirectory()
416  {
417  return is_dir($this->getZipExportDirectory());
418  }
419 
420  protected function createZipExportDirectory()
421  {
422  mkdir($this->getZipExportDirectory(), 0777, true);
423  }
424 
430  public function getZipExportDirectory()
431  {
432  return $this->external_directory_path . self::DIR_SEP . $this->client_id . self::DIR_SEP . 'tst_data'
433  . self::DIR_SEP . self::EXPORT_DIRECTORY . self::DIR_SEP . 'tst_' . $this->test_obj_id;
434  }
435 
441  public function compressTestArchive()
442  {
443  $this->updateTestArchive();
445 
446  $zip_output_path = $this->getZipExportDirectory();
447  $zip_output_filename = 'test_archive_obj_' . $this->test_obj_id . '_' . time() . '_.zip';
448 
449  ilUtil::zip($this->getTestArchive(), $zip_output_path . self::DIR_SEP . $zip_output_filename, true);
450  return;
451  }
452 
453  #endregion
454 
455  #region PassDataDirectory
456  // The pass data directory contains all data relevant for a participants pass.
457  // In addition to the test-archive-directory, this directory lives here:
458  // .../<year>/<month>/<day>/<ActiveFi>_<Pass>[_<Lastname>][_<Firstname>][_<Matriculation>]/
459  // Lastname, Firstname and Matriculation are not mandatory in the directory name.
460 
469  protected function hasPassDataDirectory($active_fi, $pass)
470  {
471  $pass_data_dir = $this->getPassDataDirectory($active_fi, $pass);
472  return is_dir($this->getPassDataDirectory($active_fi, $pass));
473  }
474 
483  protected function createPassDataDirectory($active_fi, $pass)
484  {
485  mkdir($this->getPassDataDirectory($active_fi, $pass), 0777, true);
486  return;
487  }
488 
489  private function buildPassDataDirectory($active_fi, $pass)
490  {
491  foreach ($this->archive_data_index as $data_index_entry) {
492  if ($data_index_entry != null && $data_index_entry['identifier'] == $active_fi . '|' . $pass) {
493  array_shift($data_index_entry);
494  return $this->getTestArchive() . self::DIR_SEP . implode(self::DIR_SEP, $data_index_entry);
495  }
496  }
497 
498  return null;
499  }
500 
509  protected function getPassDataDirectory($active_fi, $pass)
510  {
511  $passDataDir = $this->buildPassDataDirectory($active_fi, $pass);
512 
513  if (!$passDataDir) {
514  if ($this->getParticipantData()) {
515  $usrData = $this->getParticipantData()->getUserDataByActiveId($active_fi);
516  $user = new ilObjUser();
517  $user->setFirstname($usrData['firstname']);
518  $user->setLastname($usrData['lastname']);
519  $user->setMatriculation($usrData['matriculation']);
520  $user->setFirstname($usrData['firstname']);
521  } else {
522  global $DIC;
523  $ilUser = $DIC['ilUser'];
524  $user = $ilUser;
525  }
526 
528  date(DATE_ISO8601),
529  $active_fi,
530  $pass,
531  $user->getFirstname(),
532  $user->getLastname(),
533  $user->getMatriculation()
534  );
535 
536  $passDataDir = $this->buildPassDataDirectory($active_fi, $pass);
537  }
538 
539  return $passDataDir;
540  }
541 
552  protected function ensurePassDataDirectoryIsAvailable($active_fi, $pass)
553  {
554  if (!$this->hasPassDataDirectory($active_fi, $pass)) {
555  $this->createPassDataDirectory($active_fi, $pass);
556  }
557  return;
558  }
559 
560  #endregion
561 
562  #region PassMaterialsDirectory
563 
572  protected function hasPassMaterialsDirectory($active_fi, $pass)
573  {
575  if (@is_dir($this->getPassMaterialsDirectory($active_fi, $pass))) {
576  return true;
577  }
578  return false;
579  }
580 
589  protected function createPassMaterialsDirectory($active_fi, $pass)
590  {
591  // Data are taken from the current user as the implementation expects the first interaction of the pass
592  // takes place from the usage/behaviour of the current user.
593 
594  if ($this->getParticipantData()) {
595  $usrData = $this->getParticipantData()->getUserDataByActiveId($active_fi);
596  $user = new ilObjUser();
597  $user->setFirstname($usrData['firstname']);
598  $user->setLastname($usrData['lastname']);
599  $user->setMatriculation($usrData['matriculation']);
600  $user->setFirstname($usrData['firstname']);
601  } else {
602  global $DIC;
603  $ilUser = $DIC['ilUser'];
604  $user = $ilUser;
605  }
606 
608  date('Y'),
609  $active_fi,
610  $pass,
611  $user->getFirstname(),
612  $user->getLastname(),
613  $user->getMatriculation()
614  );
615  mkdir($this->getPassMaterialsDirectory($active_fi, $pass), 0777, true);
616  }
617 
626  protected function getPassMaterialsDirectory($active_fi, $pass)
627  {
628  $pass_data_directory = $this->getPassMaterialsDirectory($active_fi, $pass);
629  return $pass_data_directory . self::DIR_SEP . self::PASS_MATERIALS_PATH_COMPONENT;
630  }
631 
641  protected function ensurePassMaterialsDirectoryIsAvailable($active_fi, $pass)
642  {
643  if (!$this->hasPassMaterialsDirectory($active_fi, $pass)) {
644  $this->createPassMaterialsDirectory($active_fi, $pass);
645  }
646  }
647 
648  #endregion
649 
655  protected function readArchiveDataIndex()
656  {
661  $data_index_file = $this->getTestArchive() . self::DIR_SEP . self::DATA_INDEX_FILENAME;
662 
663  $contents = array();
664 
666  if (@file_exists($data_index_file)) {
667  $lines = explode("\n", file_get_contents($data_index_file));
668  foreach ($lines as $line) {
669  $line_items = explode('|', $line);
670  $line_data['identifier'] = $line_items[0] . '|' . $line_items[1];
671  $line_data['yyyy'] = $line_items[2];
672  $line_data['mm'] = $line_items[3];
673  $line_data['dd'] = $line_items[4];
674  $line_data['directory'] = $line_items[5];
675  $contents[] = $line_data;
676  }
677  }
678  return $contents;
679  }
680 
693  protected function appendToArchiveDataIndex($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation)
694  {
695  $line = $this->determinePassDataPath($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation);
696 
697  $this->archive_data_index[] = $line;
698  $output_contents = '';
699 
700  foreach ($this->archive_data_index as $line_data) {
701  if ($line_data['identifier'] == "|") {
702  continue;
703  }
704  $output_contents .= implode('|', $line_data) . "\n";
705  }
706 
707  file_put_contents($this->getTestArchive() . self::DIR_SEP . self::DATA_INDEX_FILENAME, $output_contents);
708  $this->readArchiveDataIndex();
709  return;
710  }
711 
724  protected function determinePassDataPath($date, $active_fi, $pass, $user_firstname, $user_lastname, $matriculation)
725  {
726  $date = date_create_from_format(DATE_ISO8601, $date);
727  $line = array(
728  'identifier' => $active_fi . '|' . $pass,
729  'yyyy' => date_format($date, 'Y'),
730  'mm' => date_format($date, 'm'),
731  'dd' => date_format($date, 'd'),
732  'directory' => $active_fi . '_' . $pass . '_' . $user_firstname . '_' . $user_lastname . '_' . $matriculation
733  );
734  return $line;
735  }
736 
744  protected function logArchivingProcess($message)
745  {
746  $archive = $this->getTestArchive() . self::DIR_SEP . self::ARCHIVE_LOG;
747  if (file_exists($archive)) {
748  $content = file_get_contents($archive) . "\n" . $message;
749  } else {
750  $content = $message;
751  }
752 
753  file_put_contents($archive, $content);
754  }
755 
764  protected function countFilesInDirectory($directory, $pattern = null)
765  {
766  $filecount = 0;
767 
769  if ($handle = opendir($directory)) {
770  while (($file = readdir($handle)) !== false) {
771  if (!in_array($file, array( '.', '..' )) && !is_dir($directory . $file)) {
772  if ($pattern && strpos($file, $pattern) === 0) {
773  $filecount++;
774  }
775  }
776  }
777  }
778  return $filecount;
779  }
780 }
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.
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.
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.
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
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.
__construct(Container $dic, ilPlugin $plugin)
$DIC
Definition: xapitoken.php:46
const TEST_BEST_SOLUTION_PATH_COMPONENT
$message
Definition: xapiexit.php:14
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.
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.