1<?php 2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */ 3 4/** 5 * Class ilTestArchiver 6 * 7 * Helper class to deal with the generation and maintenance of test archives. 8 * 9 * @author Maximilian Becker <mbecker@databay.de> 10 * 11 * @version $Id$ 12 * 13 * @ingroup ModulesTest 14 */ 15class ilTestArchiver 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'; 24 const QUESTION_PATH_COMPONENT_PREFIX = 'q_'; 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 80 protected $external_directory_path; /** @var $external_directory_path string External directory base path */ 81 protected $client_id; /** @var $client_id string Client id of the current client */ 82 protected $test_obj_id; /** @var $test_obj_id integer Object-ID of the test, the archiver is instantiated for */ 83 protected $archive_data_index; /** @var $archive_data_index array[string[]] Archive data index as associative array */ 84 85 protected $ilDB; /** @var $ilDB ilDBInterface */ 86 87 /** 88 * @var ilTestParticipantData 89 */ 90 protected $participantData; 91 92 #endregion 93 94 /** 95 * Returns a new ilTestArchiver object 96 * 97 * @param $test_obj_id integer Object-ID of the test, the archiver is instantiated for. 98 */ 99 public function __construct($test_obj_id) 100 { 101 /** @var $ilias ILIAS */ 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 114 /** 115 * @return ilTestParticipantData 116 */ 117 public function getParticipantData() 118 { 119 return $this->participantData; 120 } 121 122 /** 123 * @param ilTestParticipantData $participantData 124 */ 125 public function setParticipantData($participantData) 126 { 127 $this->participantData = $participantData; 128 } 129 130 #region API methods 131 132 /** 133 * Hands in a participants test submission ("a completed test") for archiving. 134 * 135 * The archive takes an html-string and a path to a PDF-file and saves it according to the archives 136 * general structure. The test is identified by active_fi and pass number, allowing to store relevant 137 * files even for anonymous tests. 138 * 139 * @param $active_fi integer Active-FI of the test participant 140 * @param $pass integer Pass-number of the actual test 141 * @param $html_string string HTML-string of the test submission 142 * @param $pdf_path string Path to a pdf representation of the test submission. 143 */ 144 public function handInParticipantSubmission($active_fi, $pass, $pdf_path, $html_string) 145 { 146 $this->ensureTestArchiveIsAvailable(); 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 161 /** 162 * Hands in a particpants question material, such as an upload or other binary content. 163 * 164 * @param $active_fi integer Active-FI of the test participant 165 * @param $pass integer Pass-number of the actual test 166 * @param $question_fi integer Question-FI of the question, the file is to be stored for. 167 * @param $original_filename string Original filename of the material to be stored. 168 * @param $file_path string Location of the file to be archived 169 */ 170 public function handInParticipantQuestionMaterial($active_fi, $pass, $question_fi, $original_filename, $file_path) 171 { 172 $this->ensureTestArchiveIsAvailable(); 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 189 /** 190 * Hands in a participants file, which is relevant for archiving but an unspecified type. 191 * 192 * Examples for such are signature files, remarks, feedback or the like. 193 * 194 * @param $active_fi integer Active-FI of the test participant 195 * @param $pass integer Pass-number of the actual test 196 * @param $original_filename string Original filename of the material to be stored. 197 * @param $file_path string Location of the file to be archived 198 */ 199 public function handInParticipantMisc($active_fi, $pass, $original_filename, $file_path) 200 { 201 $this->ensureTestArchiveIsAvailable(); 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 208 /** 209 * Hands in the best solution for a test. 210 * 211 * @param $html_string string HTML-string of the test submission 212 * @param $pdf_path string Path to a pdf representation of the test submission. 213 */ 214 public function handInTestBestSolution($html_string, $pdf_path) 215 { 216 $this->ensureTestArchiveIsAvailable(); 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 238 /** 239 * Hands in a file related to a question in context of the best solution. 240 * 241 * @param $question_fi integer QuestionFI of the question, material is to be stored for. 242 * @param $orginial_filename string Original filename of the material to be stored. 243 * @param $file_path string Path to the material to be stored. 244 */ 245 public function handInBestSolutionQuestionMaterial($question_fi, $orginial_filename, $file_path) 246 { 247 $this->ensureTestArchiveIsAvailable(); 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 272 /** 273 * Hands in an individual test result for a pass. 274 * 275 * @param $active_fi integer ActiveFI of the participant. 276 * @param $pass integer Pass of the test. 277 * @param $pdf_path string Path to the PDF containing the result. 278 * 279 * @return void 280 */ 281 public function handInTestResult($active_fi, $pass, $pdf_path) 282 { 283 $this->ensureTestArchiveIsAvailable(); 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 292 /** 293 * Hands in a test results overview. 294 * 295 * @param $html_string string HTML of the test results overview. 296 * @param $pdf_path string Path 297 */ 298 public function handInTestResultsOverview($html_string, $pdf_path) 299 { 300 $this->ensureTestArchiveIsAvailable(); 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 318 /** 319 * Returns if the archive directory structure for the test the object is created for exists. 320 * 321 * @return bool $hasTestArchive True, if the archive directory structure exists. 322 */ 323 protected function hasTestArchive() 324 { 325 return is_dir($this->getTestArchive()); 326 } 327 328 /** 329 * Creates the directory for the test archive. 330 */ 331 protected function createArchiveForTest() 332 { 333 ilUtil::makeDirParents($this->getTestArchive()); 334 //mkdir( $this->getTestArchive(), 0777, true ); 335 } 336 337 /** 338 * Returns the (theoretical) path to the archive directory of the test, this object is created for. 339 * 340 * @return string $test_archive Path to this tests archive directory. 341 */ 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 349 /** 350 * Ensures the availability of the test archive directory. 351 * 352 * Checks if the directory exists and creates it if necessary. 353 * 354 * @return void 355 */ 356 protected function ensureTestArchiveIsAvailable() 357 { 358 if (!$this->hasTestArchive()) { 359 $this->createArchiveForTest(); 360 } 361 return; 362 } 363 364 /** 365 * Replaces the test-log with the current one. 366 * 367 * @return void 368 */ 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 = ''; 375 /** @noinspection PhpAssignmentInConditionInspection */ 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'; 398 ilTestPDFGenerator::generatePDF($output_template->get(), ilTestPDFGenerator::PDF_OUTPUT_FILE, $filename, PDF_USER_RESULT); 399 400 return; 401 } 402 403 public function ensureZipExportDirectoryExists() 404 { 405 if (!$this->hasZipExportDirectory()) { 406 $this->createZipExportDirectory(); 407 } 408 } 409 410 /** 411 * Returns if the export directory for zips exists. 412 * 413 * @return bool 414 */ 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 425 /** 426 * Return the export directory, where zips are placed. 427 * 428 * @return string 429 */ 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 436 /** 437 * Generate the test archive for download. 438 * 439 * @return void 440 */ 441 public function compressTestArchive() 442 { 443 $this->updateTestArchive(); 444 $this->ensureZipExportDirectoryExists(); 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 461 /** 462 * Checks if the directory for pass data is available. 463 * 464 * @param $active_fi integer ActiveFI of the pass. 465 * @param $pass integer Pass-number of the pass. 466 * 467 * @return bool $hasPassDataDirectory True, if the pass data directory exists. 468 */ 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 475 /** 476 * Creates pass data directory 477 * 478 * @param $active_fi integer ActiveFI of the participant. 479 * @param $pass integer Pass number of the test. 480 * 481 * @return void 482 */ 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 501 /** 502 * Returns the pass data directory. 503 * 504 * @param $active_fi integer ActiveFI of the participant. 505 * @param $pass integer Pass number of the test. 506 * 507 * @return string $pass_data_directory Path to the pass data directory. 508 */ 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 527 $this->appendToArchiveDataIndex( 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 542 /** 543 * Ensures the availability of the participant data directory. 544 * 545 * Checks if the directory exists and creates it if necessary. 546 * 547 * @param $active_fi integer Active-FI of the test participant 548 * @param $pass integer Pass-number of the actual test 549 * 550 * @return void 551 */ 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 564 /** 565 * Returns if the pass materials directory exists for a given pass. 566 * 567 * @param $active_fi integer ActiveFI for the participant. 568 * @param $pass integer Pass number. 569 * 570 * @return bool $hasPassmaterialsDirectory True, if the directory exists. 571 */ 572 protected function hasPassMaterialsDirectory($active_fi, $pass) 573 { 574 /** @noinspection PhpUsageOfSilenceOperatorInspection */ 575 if (@is_dir($this->getPassMaterialsDirectory($active_fi, $pass))) { 576 return true; 577 } 578 return false; 579 } 580 581 /** 582 * Creates pass materials directory. 583 * 584 * @param $active_fi integer ActiveFI of the participant. 585 * @param $pass integer Pass number of the test. 586 * 587 * @return void 588 */ 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 607 $this->appendToArchiveDataIndex( 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 618 /** 619 * Returns the pass materials directory. 620 * 621 * @param $active_fi integer ActiveFI of the participant. 622 * @param $pass integer Pass number. 623 * 624 * @return string $pass_materials_directory Path to the pass materials directory. 625 */ 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 632 /** 633 * Ensures the availability of the pass materials directory. 634 * 635 * Checks if the directory exists and creates it if necessary. 636 * 637 * @param $active_fi integer Active-FI of the test participant 638 * @param $pass integer Pass-number of the actual test 639 * 640 */ 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 650 /** 651 * Reads the archive data index. 652 * 653 * @return array[array] $archive_data_index Archive data index. 654 */ 655 protected function readArchiveDataIndex() 656 { 657 /** 658 * The Archive Data Index is a csv-file containing the following columns 659 * <active_fi>|<pass>|<yyyy>|<mm>|<dd>|<directory> 660 */ 661 $data_index_file = $this->getTestArchive() . self::DIR_SEP . self::DATA_INDEX_FILENAME; 662 663 $contents = array(); 664 665 /** @noinspection PhpUsageOfSilenceOperatorInspection */ 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 681 /** 682 * Appends a line to the archive data index. 683 * 684 * @param $date string Date for the directories path. 685 * @param $active_fi integer ActiveFI of the participant. 686 * @param $pass integer Pass number of the participant. 687 * @param $user_firstname string User firstname. 688 * @param $user_lastname string User lastname. 689 * @param $matriculation string Matriculation number of the user. 690 * 691 * @return void 692 */ 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 712 /** 713 * Determines the pass data path. 714 * 715 * @param $date 716 * @param $active_fi 717 * @param $pass 718 * @param $user_firstname 719 * @param $user_lastname 720 * @param $matriculation 721 * 722 * @return array 723 */ 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 737 /** 738 * Logs to the archive log. 739 * 740 * @param $message string Complete log message. 741 * 742 * @return void 743 */ 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 756 /** 757 * Returns the count of files in a directory, eventually matching the given, optional, pattern. 758 * 759 * @param $directory 760 * @param null|string $pattern 761 * 762 * @return integer 763 */ 764 protected function countFilesInDirectory($directory, $pattern = null) 765 { 766 $filecount = 0; 767 768 /** @noinspection PhpAssignmentInConditionInspection */ 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} 781