ILIAS  trunk Revision v11.0_alpha-1713-gd8962da2f67
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
class.ilObjSurvey.php
Go to the documentation of this file.
1 <?php
2 
22 
26 class ilObjSurvey extends ilObject
27 {
28  public const EVALUATION_ACCESS_OFF = "0";
29  public const EVALUATION_ACCESS_ALL = "1";
30  public const EVALUATION_ACCESS_PARTICIPANTS = "2";
31 
32  public const ANONYMIZE_OFF = 0; // personalized, no codes
33  public const ANONYMIZE_ON = 1; // anonymized, codes
34  public const ANONYMIZE_FREEACCESS = 2; // anonymized, no codes
35  public const ANONYMIZE_CODE_ALL = 3; // personalized, codes
36 
37  public const QUESTIONTITLES_HIDDEN = 0;
38  public const QUESTIONTITLES_VISIBLE = 1;
39 
40  // constants to define the print view values.
41  public const PRINT_HIDE_LABELS = 1; // Show only the titles in print view
42  public const PRINT_SHOW_LABELS = 3; // Show titles and labels in print view
43 
44  //MODE TYPES
45  public const MODE_STANDARD = 0;
46  public const MODE_360 = 1;
47  public const MODE_SELF_EVAL = 2;
48  public const MODE_IND_FEEDB = 3;
49 
50  //self evaluation only access to results
51  public const RESULTS_SELF_EVAL_NONE = 0;
52  public const RESULTS_SELF_EVAL_OWN = 1;
53  public const RESULTS_SELF_EVAL_ALL = 2;
54 
55  public const RESULTS_360_NONE = 0;
56  public const RESULTS_360_OWN = 1;
57  public const RESULTS_360_ALL = 2;
58 
59  public const NOTIFICATION_PARENT_COURSE = 1;
60  public const NOTIFICATION_INVITED_USERS = 2;
61  public const NOTIFICATION_APPRAISEES = 3;
62  public const NOTIFICATION_RATERS = 4;
65 
66  protected ilLogger $svy_log;
67  protected bool $activation_limited = false;
68  protected ilObjUser $user;
71  protected bool $calculate_sum_score = false;
76  public int $survey_id = 0;
81  public string $author = "";
82  public string $introduction = "";
83  public string $outro = "";
84  // Indicates the evaluation access for learners
85  public string $evaluation_access = self::EVALUATION_ACCESS_OFF;
86  // The start date of the survey
87  public string $start_date = "";
88  // The end date of the survey
89  public string $end_date = "";
90  // The questions contained in this survey
91  public array $questions = [];
92  // Indicates the anonymization of the survey
93  public int $anonymize = 0;
94  // Indicates if the question titles are shown during a query
95  public int $display_question_titles = 0;
96  // Indicates if a survey code may be exported with the survey statistics
97  public bool $surveyCodeSecurity = false;
98 
99  public bool $mailnotification = false;
100  public string $mailaddresses = "";
101  public string $mailparticipantdata = "";
102  public bool $pool_usage = false;
103 
104  protected bool $activation_visibility = false;
105  protected ?int $activation_starting_time = null;
106  protected ?int $activation_ending_time = null;
107 
108  // 360°
109  protected bool $mode_360_self_eval = false;
110  protected bool $mode_360_self_appr = false;
111  protected bool $mode_360_self_rate = false;
112  protected int $mode_360_results = 0;
113  protected bool $mode_skill_service = false;
114 
115  // reminder/notification
116  protected bool $reminder_status = false;
118  protected ?ilDate $reminder_end = null;
119  protected int $reminder_frequency = 0;
120  protected int $reminder_target = 0;
121  protected ?string $reminder_last_sent = null;
122  protected ?int $reminder_tmpl = null;
123  protected bool $tutor_ntf_status = false;
124  protected array $tutor_ntf_recipients = [];
125  protected int $tutor_ntf_target = 0;
126  protected bool $tutor_res_status = false;
127  protected array $tutor_res_recipients = [];
128 
129  protected bool $view_own_results = false;
130  protected bool $mail_own_results = false;
131  protected bool $mail_confirmation = false;
132 
133  protected bool $anon_user_list = false;
134  protected int $mode = 0;
135  protected int $mode_self_eval_results = 0;
136 
137  protected Participants\InvitationsManager $invitation_manager;
138  protected \ILIAS\Survey\InternalService $survey_service;
139  protected \ILIAS\Survey\Code\CodeManager $code_manager;
140  protected \ILIAS\Survey\InternalDataService $data_manager;
141  protected ?Mode\FeatureConfig $feature_config;
142  protected \ILIAS\SurveyQuestionPool\Export\ImportManager $import_manager;
144 
145  public function __construct(
146  int $a_id = 0,
147  bool $a_call_by_reference = true
148  ) {
149  global $DIC;
150 
151  $this->survey_service = $DIC->survey()->internal();
152 
153  $this->user = $DIC->user();
154  $this->lng = $DIC->language();
155  $this->db = $DIC->database();
156  $this->access = $DIC->access();
157  $this->plugin_admin = $DIC["ilPluginAdmin"];
158  $this->tree = $DIC->repositoryTree();
159  $ilUser = $DIC->user();
160  $lng = $DIC->language();
161 
162  $this->type = "svy";
163  $this->survey_id = -1;
164  $this->introduction = "";
165  $this->outro = $lng->txt("survey_finished");
166  $this->author = $ilUser->getFullname();
167  $this->evaluation_access = self::EVALUATION_ACCESS_OFF;
168  $this->questions = array();
169  $this->anonymize = self::ANONYMIZE_OFF;
170  $this->display_question_titles = self::QUESTIONTITLES_VISIBLE;
171  $this->surveyCodeSecurity = true;
172  $this->pool_usage = true;
173  $this->mode = self::MODE_STANDARD;
174  $this->mode_self_eval_results = self::RESULTS_SELF_EVAL_OWN;
175 
176  $this->invitation_manager = $this
177  ->survey_service
178  ->domain()
179  ->participants()
180  ->invitations();
181 
182  $this->import_manager = $DIC->surveyQuestionPool()
183  ->internal()
184  ->domain()
185  ->import();
186  $this->placeholder_resolver = $DIC->mail()->placeholderResolver();
187 
188  parent::__construct($a_id, $a_call_by_reference);
189  $this->svy_log = ilLoggerFactory::getLogger("svy");
190  $this->initServices();
191  $this->domain = $DIC->survey()->internal()->domain();
192  }
193 
194  protected function initServices(): void
195  {
196  if ($this->getRefId() > 0) {
197  $this->code_manager = $this
198  ->survey_service
199  ->domain()
200  ->code($this, $this->user->getId());
201  }
202  $this->feature_config = $this
203  ->survey_service
204  ->domain()
205  ->modeFeatureConfig($this->getMode());
206 
207  $this->data_manager = $this
208  ->survey_service
209  ->data();
210  }
211 
212  public function create($a_upload = false): int
213  {
214  $id = parent::create();
215  if (!$a_upload) {
216  $this->createMetaData();
217  }
218  $this->setOfflineStatus(true);
219  $this->update($a_upload);
220  return $id;
221  }
222 
223  protected function doCreateMetaData(): void
224  {
225  $this->saveAuthorToMetadata();
226  }
227 
228  public function update($a_upload = false): bool
229  {
230  if (!$a_upload) {
231  $this->updateMetaData();
232  }
233 
234  if (!parent::update()) {
235  return false;
236  }
237 
238  // put here object specific stuff
239 
240  return true;
241  }
242 
243  public function createReference(): int
244  {
245  $result = parent::createReference();
246  $this->saveToDb();
247  return $result;
248  }
249 
250  public function read(): void
251  {
252  parent::read();
253  $this->loadFromDb();
254  $this->initServices();
255  }
256 
260  public function addQuestion(int $question_id): void
261  {
262  $this->questions[] = $question_id;
263  }
264 
265  public function delete(): bool
266  {
267  $this->svy_log->debug("Deleting Survey, ref id: " . $this->getRefId() . ", obj id: " .
268  $this->getId() . ", title: " . $this->getTitle());
269  $this->svy_log->debug("References: " . $this->countReferences());
270  if ($this->countReferences() === 1) {
271  $this->deleteMetaData();
272 
273  // Delete all survey questions, constraints and materials
274  foreach ($this->questions as $question_id) {
275  $this->svy_log->debug("Remove question " . $question_id);
276  $this->removeQuestion($question_id);
277  }
278  $this->deleteSurveyRecord();
279 
281  }
282 
283  $this->svy_log->debug("Call parent delete.");
284  $remove = parent::delete();
285 
286  // always call parent delete function first!!
287  if (!$remove) {
288  return false;
289  }
290  return true;
291  }
292 
297  public function deleteSurveyRecord(): void
298  {
299  $ilDB = $this->db;
300 
301  $ilDB->manipulateF(
302  "DELETE FROM svy_svy WHERE survey_id = %s",
303  array('integer'),
304  array($this->getSurveyId())
305  );
306 
307  $result = $ilDB->queryF(
308  "SELECT questionblock_fi FROM svy_qblk_qst WHERE survey_fi = %s",
309  array('integer'),
310  array($this->getSurveyId())
311  );
312  $questionblocks = array();
313  while ($row = $ilDB->fetchAssoc($result)) {
314  $questionblocks[] = $row["questionblock_fi"];
315  }
316  if (count($questionblocks)) {
317  $affectedRows = $ilDB->manipulate("DELETE FROM svy_qblk WHERE " . $ilDB->in('questionblock_id', $questionblocks, false, 'integer'));
318  }
319  $ilDB->manipulateF(
320  "DELETE FROM svy_qblk_qst WHERE survey_fi = %s",
321  array('integer'),
322  array($this->getSurveyId())
323  );
324  $this->deleteAllUserData(false);
325 
326  if (isset($this->code_manager)) {
327  $this->code_manager->deleteAll(true);
328  }
329 
330  // delete export files
331  $svy_data_dir = ilFileUtils::getDataDir() . "/svy_data";
332  $directory = $svy_data_dir . "/svy_" . $this->getId();
333  if (is_dir($directory)) {
334  ilFileUtils::delDir($directory);
335  }
336 
337  $mobs = ilObjMediaObject::_getMobsOfObject("svy:html", $this->getId());
338  // remaining usages are not in text anymore -> delete them
339  // and media objects (note: delete method of ilObjMediaObject
340  // checks whether object is used in another context; if yes,
341  // the object is not deleted!)
342  foreach ($mobs as $mob) {
343  ilObjMediaObject::_removeUsage($mob, "svy:html", $this->getId());
344  $mob_obj = new ilObjMediaObject($mob);
345  $mob_obj->delete();
346  }
347  }
348 
354  public function deleteAllUserData(
355  bool $reset_LP = true
356  ): void {
357  $ilDB = $this->db;
358 
359  $result = $ilDB->queryF(
360  "SELECT finished_id FROM svy_finished WHERE survey_fi = %s",
361  array('integer'),
362  array($this->getSurveyId())
363  );
364  $active_array = array();
365  while ($row = $ilDB->fetchAssoc($result)) {
366  $active_array[] = $row["finished_id"];
367  }
368 
369  $affectedRows = $ilDB->manipulateF(
370  "DELETE FROM svy_finished WHERE survey_fi = %s",
371  array('integer'),
372  array($this->getSurveyId())
373  );
374 
375  foreach ($active_array as $active_fi) {
376  $affectedRows = $ilDB->manipulateF(
377  "DELETE FROM svy_answer WHERE active_fi = %s",
378  array('integer'),
379  array($active_fi)
380  );
381  $affectedRows = $ilDB->manipulateF(
382  "DELETE FROM svy_times WHERE finished_fi = %s",
383  array('integer'),
384  array($active_fi)
385  );
386  }
387 
388  if ($reset_LP) {
389  $lp_obj = ilObjectLP::getInstance($this->getId());
390  $lp_obj->resetLPDataForCompleteObject();
391  }
392 
393  $this->invitation_manager->removeAll($this->getSurveyId());
394  }
395 
401  array $finished_ids
402  ): void {
403  $ilDB = $this->db;
404 
405  $user_ids = [];
406 
407  foreach ($finished_ids as $finished_id) {
408  $result = $ilDB->queryF(
409  "SELECT finished_id, user_fi FROM svy_finished WHERE finished_id = %s",
410  array('integer'),
411  array($finished_id)
412  );
413  $row = $ilDB->fetchAssoc($result);
414  if ($row["user_fi"]) {
415  $user_ids[] = (int) $row["user_fi"];
416  }
417 
418  $affectedRows = $ilDB->manipulateF(
419  "DELETE FROM svy_answer WHERE active_fi = %s",
420  array('integer'),
421  array($row["finished_id"])
422  );
423 
424  $affectedRows = $ilDB->manipulateF(
425  "DELETE FROM svy_finished WHERE finished_id = %s",
426  array('integer'),
427  array($finished_id)
428  );
429 
430  $affectedRows = $ilDB->manipulateF(
431  "DELETE FROM svy_times WHERE finished_fi = %s",
432  array('integer'),
433  array($row["finished_id"])
434  );
435  }
436 
437  if (count($user_ids)) {
438  $lp_obj = ilObjectLP::getInstance($this->getId());
439  $lp_obj->resetLPDataForUserIds($user_ids);
440 
441  // remove invitations, if exist
442  foreach ($user_ids as $user_id) {
443  $this->invitation_manager->remove($this->getSurveyId(), $user_id);
444  }
445  }
446  }
447 
451  public function getSurveyParticipants(
452  ?array $finished_ids = null,
453  bool $force_non_anonymous = false,
454  bool $include_invites = false
455  ): array {
456  $ilDB = $this->db;
457  $sql = "SELECT * FROM svy_finished" .
458  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer");
459  if ($finished_ids) {
460  $sql .= " AND " . $ilDB->in("finished_id", $finished_ids, "", "integer");
461  }
462 
463  $result = $ilDB->query($sql);
464  $participants = array();
465  if ($result->numRows() > 0) {
466  while ($row = $ilDB->fetchAssoc($result)) {
467  $userdata = $this->getUserDataFromActiveId($row["finished_id"], $force_non_anonymous);
468  $userdata["finished"] = (bool) $row["state"];
469  $userdata["finished_tstamp"] = $row["tstamp"];
470  $participants[$userdata["sortname"] . $userdata["active_id"]] = $userdata;
471  }
472  }
473  $participant_ids = array_column($participants, "usr_id");
474  if ($include_invites) {
475  foreach ($this->invitation_manager->getAllForSurvey($this->getSurveyId()) as $usr_id) {
476  if (!in_array($usr_id, $participant_ids)) {
477  $name = ilObjUser::_lookupName($usr_id);
478  $participants[$name["lastname"] . "," . $name["firstname"] . $usr_id] = [
479  "fullname" => ilObjUser::_lookupFullname($usr_id),
480  "sortname" => $name["lastname"] . "," . $name["firstname"],
481  "fistname" => $name["firstname"],
482  "lastname" => $name["lastname"],
483  "login" => $name["login"],
484  "gender" => "",
485  "usr_id" => $usr_id,
486  "finished" => false,
487  "finished_tstamp" => 0,
488  "invited" => true
489  ];
490  }
491  }
492  }
493  return $participants;
494  }
495 
500  public function isComplete(): bool
501  {
502  return ($this->getTitle() && count($this->questions));
503  }
504 
505  public function hasQuestions(): bool
506  {
507  return count($this->questions);
508  }
509 
514  public function saveCompletionStatus(): void
515  {
516  $db = $this->db;
517  if ($this->getSurveyId() > 0) {
518  $db->manipulateF(
519  "UPDATE svy_svy SET complete = %s, tstamp = %s WHERE survey_id = %s",
520  array('text','integer','integer'),
521  array($this->isComplete(), time(), $this->getSurveyId())
522  );
523  }
524  }
525 
530  public function duplicateQuestionForSurvey(
531  int $question_id,
532  bool $a_force = false
533  ): int {
534  $questiontype = $this->getQuestionType($question_id);
535  $question_gui = $this->getQuestionGUI($questiontype, $question_id);
536 
537  // check if question is a pool question at all, if not do nothing
538  if ($this->getId() === $question_gui->object->getObjId() && !$a_force) {
539  return $question_id;
540  }
541 
542  $duplicate_id = $question_gui->object->duplicate(true, "", "", 0, $this->getId());
543  return $duplicate_id;
544  }
545 
550  public function insertQuestion(
551  int $question_id
552  ): bool {
553  $ilDB = $this->db;
554 
555  $this->svy_log->debug("insert question, id:" . $question_id);
556 
557  if (!SurveyQuestion::_isComplete($question_id)) {
558  $this->svy_log->debug("question is not complete");
559  return false;
560  } else {
561  // get maximum sequence index in test
562  $result = $ilDB->queryF(
563  "SELECT survey_question_id FROM svy_svy_qst WHERE survey_fi = %s",
564  array('integer'),
565  array($this->getSurveyId())
566  );
567  $sequence = $result->numRows();
568  $duplicate_id = $this->duplicateQuestionForSurvey($question_id);
569  $this->svy_log->debug("duplicate, id: " . $question_id . ", duplicate id: " . $duplicate_id);
570 
571  // check if question is not already in the survey, see #22018
572  if ($this->isQuestionInSurvey($duplicate_id)) {
573  return false;
574  }
575 
576  $next_id = $ilDB->nextId('svy_svy_qst');
577  $affectedRows = $ilDB->manipulateF(
578  "INSERT INTO svy_svy_qst (survey_question_id, survey_fi, question_fi, sequence, tstamp) VALUES (%s, %s, %s, %s, %s)",
579  array('integer', 'integer', 'integer', 'integer', 'integer'),
580  array($next_id, $this->getSurveyId(), $duplicate_id, $sequence, time())
581  );
582 
583  $this->svy_log->debug("added entry to svy_svy_qst, id: " . $next_id . ", question id: " . $duplicate_id . ", sequence: " . $sequence);
584 
585  $this->loadQuestionsFromDb();
586  $this->saveCompletionStatus();
587  return true;
588  }
589  }
590 
591  // Check if a question is already in the survey
592  public function isQuestionInSurvey(
593  int $a_question_fi
594  ): bool {
595  global $DIC;
596  //return false;
597  $ilDB = $DIC->database();
598 
599  $set = $ilDB->query("SELECT * FROM svy_svy_qst " .
600  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
601  " AND question_fi = " . $ilDB->quote($a_question_fi, "integer"));
602  if ($rec = $ilDB->fetchAssoc($set)) {
603  return true;
604  }
605  return false;
606  }
607 
608  // Inserts a questionblock in the survey
609  public function insertQuestionblock(
610  int $questionblock_id
611  ): void {
612 
613  $sequence_manager = $this->survey_service->domain()->sequence(
614  $this->getSurveyId(),
615  $this
616  );
617 
618  $ilDB = $this->db;
619  $result = $ilDB->queryF(
620  "SELECT svy_qblk.title, svy_qblk.show_questiontext, svy_qblk.show_blocktitle," .
621  " svy_qblk_qst.question_fi FROM svy_qblk, svy_qblk_qst, svy_svy_qst" .
622  " WHERE svy_qblk.questionblock_id = svy_qblk_qst.questionblock_fi" .
623  " AND svy_svy_qst.question_fi = svy_qblk_qst.question_fi" .
624  " AND svy_qblk.questionblock_id = %s" .
625  " ORDER BY svy_svy_qst.sequence",
626  array('integer'),
627  array($questionblock_id)
628  );
629  $questions = array();
630  $show_questiontext = false;
631  $show_blocktitle = false;
632  $title = "";
633  while ($row = $ilDB->fetchAssoc($result)) {
634  //$duplicate_id = $this->duplicateQuestionForSurvey($row["question_fi"]);
635  $duplicate_id = $sequence_manager->appendQuestion($row["question_fi"], true);
636  $questions[] = $duplicate_id;
637  $title = (string) $row["title"];
638  $show_questiontext = (bool) $row["show_questiontext"];
639  $show_blocktitle = (bool) $row["show_blocktitle"];
640  }
641  $this->createQuestionblock($title, $show_questiontext, $show_blocktitle, $questions);
642  }
643 
644  // seems to be only be used for code mails
645  public function saveUserSettings(
646  int $usr_id,
647  string $key,
648  string $title,
649  string $value
650  ): void {
651  $ilDB = $this->db;
652 
653  $next_id = $ilDB->nextId('svy_settings');
654  $affectedRows = $ilDB->insert("svy_settings", array(
655  "settings_id" => array("integer", $next_id),
656  "usr_id" => array("integer", $usr_id),
657  "keyword" => array("text", $key),
658  "title" => array("text", $title),
659  "value" => array("clob", $value)
660  ));
661  }
662 
663  public function deleteUserSettings(
664  int $id
665  ): void {
666  $ilDB = $this->db;
667 
668  $ilDB->manipulateF(
669  "DELETE FROM svy_settings WHERE settings_id = %s",
670  array('integer'),
671  array($id)
672  );
673  }
674 
675  public function getUserSettings(
676  int $usr_id,
677  string $key
678  ): array {
679  $ilDB = $this->db;
680 
681  $result = $ilDB->queryF(
682  "SELECT * FROM svy_settings WHERE usr_id = %s AND keyword = %s",
683  array('integer', 'text'),
684  array($usr_id, $key)
685  );
686  $found = array();
687  if ($result->numRows()) {
688  while ($row = $ilDB->fetchAssoc($result)) {
689  $found[$row['settings_id']] = $row;
690  }
691  }
692  return $found;
693  }
694 
695  // Saves a survey object to a database
696  public function saveToDb(): void
697  {
698  $ilDB = $this->db;
699 
700  // date handling
701  $rmd_start = $this->getReminderStart();
702  if (is_object($rmd_start)) {
703  $rmd_start = $rmd_start->get(IL_CAL_DATE);
704  }
705  $rmd_end = $this->getReminderEnd();
706  if (is_object($rmd_end)) {
707  $rmd_end = $rmd_end->get(IL_CAL_DATE);
708  }
709  if ($this->getSurveyId() < 1) {
710  $next_id = $ilDB->nextId('svy_svy');
711  $affectedRows = $ilDB->insert("svy_svy", array(
712  "survey_id" => array("integer", $next_id),
713  "obj_fi" => array("integer", $this->getId()),
714  "author" => array("text", $this->getAuthor()),
715  "introduction" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getIntroduction(), 0)),
716  "outro" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getOutro(), 0)),
717  "startdate" => array("text", $this->getStartDate()),
718  "enddate" => array("text", $this->getEndDate()),
719  "evaluation_access" => array("text", $this->getEvaluationAccess()),
720  "complete" => array("text", $this->isComplete()),
721  "created" => array("integer", time()),
722  "anonymize" => array("text", $this->getAnonymize()),
723  "show_question_titles" => array("text", $this->getShowQuestionTitles()),
724  "mailnotification" => array('integer', ($this->getMailNotification()) ? 1 : 0),
725  "mailaddresses" => array('text', $this->getMailAddresses()),
726  "mailparticipantdata" => array('text', $this->getMailParticipantData()),
727  "tstamp" => array("integer", time()),
728  // Mode type
729  "mode" => array("integer", $this->getMode()),
730  // 360°
731  "mode_360_self_eval" => array("integer", $this->get360SelfEvaluation()),
732  "mode_360_self_rate" => array("integer", $this->get360SelfRaters()),
733  "mode_360_self_appr" => array("integer", $this->get360SelfAppraisee()),
734  "mode_360_results" => array("integer", $this->get360Results()),
735  // competences
736  "mode_skill_service" => array("integer", (int) $this->getSkillService()),
737  // Self Evaluation Only
738  "mode_self_eval_results" => array("integer", self::RESULTS_SELF_EVAL_OWN),
739  // reminder/notification
740  "reminder_status" => array("integer", (int) $this->getReminderStatus()),
741  "reminder_start" => array("datetime", $rmd_start),
742  "reminder_end" => array("datetime", $rmd_end),
743  "reminder_frequency" => array("integer", $this->getReminderFrequency()),
744  "reminder_target" => array("integer", $this->getReminderTarget()),
745  "reminder_last_sent" => array("datetime", $this->getReminderLastSent()),
746  "reminder_tmpl" => array("text", $this->getReminderTemplate(true)),
747  "tutor_ntf_status" => array("integer", (int) $this->getTutorNotificationStatus()),
748  "tutor_ntf_reci" => array("text", implode(";", $this->getTutorNotificationRecipients())),
749  "tutor_ntf_target" => array("integer", $this->getTutorNotificationTarget()),
750  "own_results_view" => array("integer", $this->hasViewOwnResults()),
751  "own_results_mail" => array("integer", $this->hasMailOwnResults()),
752  "tutor_res_status" => array("integer", (int) $this->getTutorResultsStatus()),
753  "tutor_res_reci" => array("text", implode(";", $this->getTutorResultsRecipients())),
754  "confirmation_mail" => array("integer", $this->hasMailConfirmation()),
755  "anon_user_list" => array("integer", $this->hasAnonymousUserList()),
756  "calculate_sum_score" => array("integer", $this->getCalculateSumScore())
757  ));
758  $this->setSurveyId($next_id);
759  } else {
760  $affectedRows = $ilDB->update("svy_svy", array(
761  "author" => array("text", $this->getAuthor()),
762  "introduction" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getIntroduction(), 0)),
763  "outro" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getOutro(), 0)),
764  "startdate" => array("text", $this->getStartDate()),
765  "enddate" => array("text", $this->getEndDate()),
766  "evaluation_access" => array("text", $this->getEvaluationAccess()),
767  "complete" => array("text", $this->isComplete()),
768  "anonymize" => array("text", $this->getAnonymize()),
769  "show_question_titles" => array("text", $this->getShowQuestionTitles()),
770  "mailnotification" => array('integer', ($this->getMailNotification()) ? 1 : 0),
771  "mailaddresses" => array('text', $this->getMailAddresses()),
772  "mailparticipantdata" => array('text', $this->getMailParticipantData()),
773  "tstamp" => array("integer", time()),
774  //MODE TYPE
775  "mode" => array("integer", $this->getMode()),
776  // 360°
777  "mode_360_self_eval" => array("integer", $this->get360SelfEvaluation()),
778  "mode_360_self_rate" => array("integer", $this->get360SelfRaters()),
779  "mode_360_self_appr" => array("integer", $this->get360SelfAppraisee()),
780  "mode_360_results" => array("integer", $this->get360Results()),
781  // Competences
782  "mode_skill_service" => array("integer", (int) $this->getSkillService()),
783  // Self Evaluation Only
784  "mode_self_eval_results" => array("integer", $this->getSelfEvaluationResults()),
785  // reminder/notification
786  "reminder_status" => array("integer", $this->getReminderStatus()),
787  "reminder_start" => array("datetime", $rmd_start),
788  "reminder_end" => array("datetime", $rmd_end),
789  "reminder_frequency" => array("integer", $this->getReminderFrequency()),
790  "reminder_target" => array("integer", $this->getReminderTarget()),
791  "reminder_last_sent" => array("datetime", $this->getReminderLastSent()),
792  "reminder_tmpl" => array("text", $this->getReminderTemplate()),
793  "tutor_ntf_status" => array("integer", $this->getTutorNotificationStatus()),
794  "tutor_ntf_reci" => array("text", implode(";", $this->getTutorNotificationRecipients())),
795  "tutor_ntf_target" => array("integer", $this->getTutorNotificationTarget()),
796  "own_results_view" => array("integer", $this->hasViewOwnResults()),
797  "own_results_mail" => array("integer", $this->hasMailOwnResults()),
798  "tutor_res_status" => array("integer", (int) $this->getTutorResultsStatus()),
799  "tutor_res_reci" => array("text", implode(";", $this->getTutorResultsRecipients())),
800  "confirmation_mail" => array("integer", $this->hasMailConfirmation()),
801  "anon_user_list" => array("integer", $this->hasAnonymousUserList()),
802  "calculate_sum_score" => array("integer", $this->getCalculateSumScore())
803  ), array(
804  "survey_id" => array("integer", $this->getSurveyId())
805  ));
806  }
807  if ($affectedRows) {
808  // save questions to db
809  $this->saveQuestionsToDb();
810  }
811 
812  // moved activation to ilObjectActivation
813  if ($this->ref_id) {
814  ilObjectActivation::getItem($this->ref_id);
815 
816  $item = new ilObjectActivation();
817  if (!$this->isActivationLimited()) {
818  $item->setTimingType(ilObjectActivation::TIMINGS_DEACTIVATED);
819  } else {
820  $item->setTimingType(ilObjectActivation::TIMINGS_ACTIVATION);
821  $item->setTimingStart($this->getActivationStartDate());
822  $item->setTimingEnd($this->getActivationEndDate());
823  $item->toggleVisible($this->getActivationVisibility());
824  }
825 
826  $item->update($this->ref_id);
827  }
828  }
829 
830  // Saves the survey questions to db
831  public function saveQuestionsToDb(): void
832  {
833  $ilDB = $this->db;
834 
835  $this->svy_log->debug("save questions");
836 
837  // gather old questions state
838  $old_questions = array();
839  $result = $ilDB->queryF(
840  "SELECT survey_question_id,question_fi,sequence" .
841  " FROM svy_svy_qst WHERE survey_fi = %s",
842  array('integer'),
843  array($this->getSurveyId())
844  );
845  while ($row = $ilDB->fetchAssoc($result)) {
846  $old_questions[$row["question_fi"]] = $row; // problem, as soon as duplicates exist, they will be hidden here
847  }
848 
849  // #15231 - diff with current questions state
850  $insert = $update = $delete = array();
851  foreach ($this->questions as $seq => $fi) {
852  if (!array_key_exists($fi, $old_questions)) { // really new fi IDs
853  $insert[] = $fi; // this should be ok, should not create duplicates here
854  } elseif ($old_questions[$fi]["sequence"] != $seq) { // we are updating one of the duplicates (if any)
855  $update[$fi] = $old_questions[$fi]["survey_question_id"];
856  }
857  // keep track of still relevant questions
858  unset($old_questions[$fi]); // deleting old question, if they are not in current array
859  }
860 
861  // delete obsolete question relations
862  if (count($old_questions)) {
863  $del_ids = array();
864  foreach ($old_questions as $fi => $old) {
865  $del_ids[] = $old["survey_question_id"];
866  }
867  $ilDB->manipulate($q = "DELETE FROM svy_svy_qst" .
868  " WHERE " . $ilDB->in("survey_question_id", $del_ids, "", "integer"));
869  $this->svy_log->debug("delete: " . $q);
870  }
871  unset($old_questions);
872 
873  // create/update question relations
874  foreach ($this->questions as $seq => $fi) {
875  if (in_array($fi, $insert)) {
876  // check if question is not already in the survey, see #22018
877  if (!$this->isQuestionInSurvey($fi)) {
878  $next_id = $ilDB->nextId('svy_svy_qst');
879  $ilDB->manipulateF(
880  "INSERT INTO svy_svy_qst" .
881  " (survey_question_id, survey_fi, question_fi, heading, sequence, tstamp)" .
882  " VALUES (%s, %s, %s, %s, %s, %s)",
883  array('integer', 'integer', 'integer', 'text', 'integer', 'integer'),
884  array($next_id, $this->getSurveyId(), $fi, null, $seq, time())
885  );
886  $this->svy_log->debug("insert svy_svy_qst, id:" . $next_id . ", fi: " . $fi . ", seq:" . $seq);
887  }
888  } elseif (array_key_exists($fi, $update)) {
889  $ilDB->manipulate("UPDATE svy_svy_qst" .
890  " SET sequence = " . $ilDB->quote($seq, "integer") .
891  ", tstamp = " . $ilDB->quote(time(), "integer") .
892  " WHERE survey_question_id = " . $ilDB->quote($update[$fi], "integer"));
893  $this->svy_log->debug("update svy_svy_qst, id:" . $update[$fi] . ", fi: " . $fi . ", seq:" . $seq);
894  }
895  }
896  }
897 
898  // Returns a question gui object to a given questiontype and question id
899  public function getQuestionGUI(
900  string $questiontype,
901  int $question_id
902  ): SurveyQuestionGUI {
903  return SurveyQuestionGUI::_getQuestionGUI($questiontype, $question_id);
904  }
905 
906  // Returns the question type of a question with a given id
907  public function getQuestionType(
908  int $question_id
909  ): string {
910  $ilDB = $this->db;
911  if ($question_id < 1) {
912  return -1;
913  }
914  $result = $ilDB->queryF(
915  "SELECT type_tag FROM svy_question, svy_qtype WHERE svy_question.question_id = %s AND " .
916  "svy_question.questiontype_fi = svy_qtype.questiontype_id",
917  array('integer'),
918  array($question_id)
919  );
920  if ($result->numRows() === 1) {
921  $data = $ilDB->fetchAssoc($result);
922  return $data["type_tag"];
923  } else {
924  return "";
925  }
926  }
927 
928  public function getSurveyId(): int
929  {
930  return $this->survey_id;
931  }
932 
936  public function setAnonymize(int $a_anonymize): void
937  {
938  switch ($a_anonymize) {
939  case self::ANONYMIZE_OFF:
940  case self::ANONYMIZE_ON:
941  case self::ANONYMIZE_FREEACCESS:
942  case self::ANONYMIZE_CODE_ALL:
943  $this->anonymize = $a_anonymize;
944  break;
945  default:
946  $this->anonymize = self::ANONYMIZE_OFF;
947  break;
948  }
949  }
950 
951  public function getAnonymize(): int
952  {
953  return $this->anonymize;
954  }
955 
956  public function setCalculateSumScore(
957  bool $a_val
958  ): void {
959  $this->calculate_sum_score = $a_val;
960  }
961 
962  public function getCalculateSumScore(): bool
963  {
965  }
966 
967  // Checks if the survey is accessible without a survey code
968  public function isAccessibleWithoutCode(): bool
969  {
970  return ($this->getAnonymize() === self::ANONYMIZE_OFF ||
971  $this->getAnonymize() === self::ANONYMIZE_FREEACCESS);
972  }
973 
974  // Checks if the survey results are to be anonymized
975  public function hasAnonymizedResults(): bool
976  {
977  return ($this->getAnonymize() === self::ANONYMIZE_ON ||
978  $this->getAnonymize() === self::ANONYMIZE_FREEACCESS);
979  }
980 
981  public function loadFromDb(): void
982  {
983  $ilDB = $this->db;
984  $result = $ilDB->queryF(
985  "SELECT * FROM svy_svy WHERE obj_fi = %s",
986  array('integer'),
987  array($this->getId())
988  );
989  if ($result->numRows() === 1) {
990  $data = $ilDB->fetchAssoc($result);
991  $this->setSurveyId($data["survey_id"]);
992  $this->setAuthor($data["author"] ?? "");
993  $this->setIntroduction(ilRTE::_replaceMediaObjectImageSrc((string) $data["introduction"], 1));
994  if (strcmp($data["outro"], "survey_finished") === 0) {
995  $this->setOutro($this->lng->txt("survey_finished"));
996  } else {
997  $this->setOutro(ilRTE::_replaceMediaObjectImageSrc((string) $data["outro"], 1));
998  }
999  $this->setShowQuestionTitles((bool) $data["show_question_titles"]);
1000  $this->setStartDate((string) ($data["startdate"] ?? ""));
1001  $this->setEndDate((string) ($data["enddate"] ?? ""));
1002  $this->setAnonymize((int) $data["anonymize"]);
1003  $this->setEvaluationAccess($data["evaluation_access"] ?? "");
1004  $this->loadQuestionsFromDb();
1005  $this->setMailNotification((bool) $data['mailnotification']);
1006  $this->setMailAddresses((string) $data['mailaddresses']);
1007  $this->setMailParticipantData((string) $data['mailparticipantdata']);
1008  $this->setPoolUsage((bool) $data['pool_usage']);
1009  // Mode
1010  $this->setMode($data['mode']);
1011  // 360°
1012  $this->set360SelfEvaluation((bool) $data['mode_360_self_eval']);
1013  $this->set360SelfRaters((bool) $data['mode_360_self_rate']);
1014  $this->set360SelfAppraisee((bool) $data['mode_360_self_appr']);
1015  $this->set360Results((int) $data['mode_360_results']);
1016  // Mode self evaluated
1017  $this->setSelfEvaluationResults((int) $data['mode_self_eval_results']);
1018  // Competences
1019  $this->setSkillService((bool) $data['mode_skill_service']);
1020  // reminder/notification
1021  $this->setReminderStatus((bool) $data["reminder_status"]);
1022  $this->setReminderStart($data["reminder_start"] ? new ilDate($data["reminder_start"], IL_CAL_DATE) : null);
1023  $this->setReminderEnd($data["reminder_end"] ? new ilDate($data["reminder_end"], IL_CAL_DATE) : null);
1024  $this->setReminderFrequency((int) $data["reminder_frequency"]);
1025  $this->setReminderTarget((int) $data["reminder_target"]);
1026  $this->setReminderLastSent((string) $data["reminder_last_sent"]);
1027  $this->setReminderTemplate((int) $data["reminder_tmpl"]);
1028  $this->setTutorNotificationStatus($data["tutor_ntf_status"]);
1029  $this->setTutorNotificationRecipients(explode(";", $data["tutor_ntf_reci"] ?? ""));
1030  $this->setTutorNotificationTarget($data["tutor_ntf_target"]);
1031  $this->setTutorResultsStatus((bool) $data["tutor_res_status"]);
1032  $this->setTutorResultsRecipients(explode(";", $data["tutor_res_reci"] ?? ""));
1033 
1034  $this->setMailOwnResults((bool) $data["own_results_mail"]);
1035  $this->setMailConfirmation((bool) $data["confirmation_mail"]);
1036  $this->setCalculateSumScore((bool) $data["calculate_sum_score"]);
1037 
1038  $this->setAnonymousUserList((bool) $data["anon_user_list"]);
1039  }
1040 
1041  // moved activation to ilObjectActivation
1042  if (isset($this->ref_id) && $this->ref_id !== 0) {
1043  $activation = ilObjectActivation::getItem($this->ref_id);
1044  switch ($activation["timing_type"]) {
1046  $this->setActivationLimited(true);
1047  $this->setActivationStartDate($activation["timing_start"]);
1048  $this->setActivationEndDate($activation["timing_end"]);
1049  $this->setActivationVisibility($activation["visible"]);
1050  break;
1051 
1052  default:
1053  $this->setActivationLimited(false);
1054  break;
1055  }
1056  }
1057  }
1058 
1059  public function loadQuestionsFromDb(): void
1060  {
1061  $ilDB = $this->db;
1062  $this->questions = array();
1063  $result = $ilDB->queryF(
1064  "SELECT * FROM svy_svy_qst WHERE survey_fi = %s ORDER BY sequence",
1065  array('integer'),
1066  array($this->getSurveyId())
1067  );
1068  while ($data = $ilDB->fetchAssoc($result)) {
1069  $this->questions[$data["sequence"]] = $data["question_fi"];
1070  }
1071  }
1072 
1073  // Remove duplicate sequence entries, see #22018
1074  public function fixSequenceStructure(): void
1075  {
1076  global $DIC;
1077 
1078  $ilDB = $DIC->database();
1079  //return;
1080  // we keep all survey question ids with their lowest sequence
1081  $result = $ilDB->queryF(
1082  "SELECT * FROM svy_svy_qst WHERE survey_fi = %s ORDER BY sequence",
1083  array('integer'),
1084  array($this->getSurveyId())
1085  );
1086 
1087  // step 1: find duplicates -> $to_delete_ids
1088  $fis = array();
1089  $to_delete_ids = array();
1090  while ($data = $ilDB->fetchAssoc($result)) {
1091  if (in_array($data["question_fi"], $fis)) { // found a duplicate
1092  $to_delete_ids[] = $data["survey_question_id"];
1093  } else {
1094  $fis[] = $data["question_fi"];
1095  }
1096  }
1097 
1098  // step 2: we delete the duplicates
1099  if (count($to_delete_ids) > 0) {
1100  $ilDB->manipulate($q = "DELETE FROM svy_svy_qst" .
1101  " WHERE " . $ilDB->in("survey_question_id", $to_delete_ids, false, "integer") .
1102  " AND survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer"));
1103  $this->svy_log->debug("delete: " . $q);
1104 
1105  $ilDB->manipulate($q = "DELETE FROM svy_qblk_qst " .
1106  " WHERE " . $ilDB->in("question_fi", $fis, true, "integer") .
1107  " AND survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer"));
1108  $this->svy_log->debug("delete: " . $q);
1109  }
1110 
1111  // step 3: we fix the sequence
1112  $set = $ilDB->query("SELECT * FROM svy_svy_qst " .
1113  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") . " ORDER BY sequence");
1114  $seq = 0;
1115  while ($rec = $ilDB->fetchAssoc($set)) {
1116  $ilDB->manipulate(
1117  $q = "UPDATE svy_svy_qst SET " .
1118  " sequence = " . $ilDB->quote($seq++, "integer") .
1119  " WHERE survey_question_id = " . $ilDB->quote($rec["survey_question_id"], "integer")
1120  );
1121  $this->svy_log->debug("update: " . $q);
1122  }
1123  }
1124 
1125  public function setAuthor(
1126  string $author = ""
1127  ): void {
1128  $this->author = $author;
1129  }
1130 
1136  public function saveAuthorToMetadata(
1137  string $a_author = ""
1138  ): void {
1139  if ($a_author === '') {
1140  $ilUser = $this->user;
1141  $a_author = $ilUser->getFullname();
1142  }
1143  $this->domain->metadata()->saveAuthorsInLOMIfNoLifecycleSet(
1144  $this->getId(),
1145  0,
1146  $this->getType(),
1147  $a_author,
1148  );
1149  }
1150 
1151  // Gets the authors name from metadata
1152  public function getAuthor(): string
1153  {
1154  return $this->domain->metadata()->getAuthorsFromLOM(
1155  $this->getId(),
1156  0,
1157  $this->getType()
1158  );
1159  }
1160 
1161  public function getShowQuestionTitles(): bool
1162  {
1163  return (bool) $this->display_question_titles;
1164  }
1165 
1166  public function setShowQuestionTitles(bool $a_show): void
1167  {
1168  $this->display_question_titles = $a_show;
1169  }
1170 
1171  public function setIntroduction(
1172  string $introduction = ""
1173  ): void {
1174  $this->introduction = $introduction;
1175  }
1176 
1177  public function setOutro(
1178  string $outro = ""
1179  ): void {
1180  $this->outro = $outro;
1181  }
1182 
1183  public function getStartDate(): string
1184  {
1185  return $this->start_date;
1186  }
1187 
1191  public function setStartDate(
1192  string $start_date = ""
1193  ): void {
1194  $this->start_date = $start_date;
1195  }
1196 
1201  public function setStartDateAndTime(
1202  string $start_date,
1203  string $start_time
1204  ): void {
1205  $y = '';
1206  $m = '';
1207  $d = '';
1208  $h = '';
1209  $i = '';
1210  $s = '';
1211  if (preg_match("/(\d{4})-(\d{2})-(\d{2})/", $start_date, $matches)) {
1212  $y = $matches[1];
1213  $m = $matches[2];
1214  $d = $matches[3];
1215  }
1216  if (preg_match("/(\d{2}):(\d{2}):(\d{2})/", $start_time, $matches)) {
1217  $h = $matches[1];
1218  $i = $matches[2];
1219  $s = $matches[3];
1220  }
1221  $this->start_date = sprintf('%04d%02d%02d%02d%02d%02d', $y, $m, $d, $h, $i, $s);
1222  }
1223 
1224  public function getEndDate(): string
1225  {
1226  return $this->end_date;
1227  }
1228 
1232  public function setEndDate(
1233  string $end_date = ""
1234  ): void {
1235  $this->end_date = $end_date;
1236  }
1237 
1238  public function hasStarted(): bool
1239  {
1240  $start = $this->getStartDate();
1241  if ($start) {
1242  $start_date = new ilDateTime($start, IL_CAL_TIMESTAMP);
1243  return ($start_date->get(IL_CAL_UNIX) < time());
1244  }
1245  return true;
1246  }
1247 
1248  public function hasEnded(): bool
1249  {
1250  $end = $this->getEndDate();
1251  if ($end) {
1252  $end_date = new ilDateTime($end, IL_CAL_TIMESTAMP);
1253  return ($end_date->get(IL_CAL_UNIX) < time());
1254  }
1255  return false;
1256  }
1257 
1262  public function setEndDateAndTime(
1263  string $end_date,
1264  string $end_time
1265  ): void {
1266  $y = '';
1267  $m = '';
1268  $d = '';
1269  $h = '';
1270  $i = '';
1271  $s = '';
1272  if (preg_match("/(\d{4})-(\d{2})-(\d{2})/", $end_date, $matches)) {
1273  $y = $matches[1];
1274  $m = $matches[2];
1275  $d = $matches[3];
1276  }
1277  if (preg_match("/(\d{2}):(\d{2}):(\d{2})/", $end_time, $matches)) {
1278  $h = $matches[1];
1279  $i = $matches[2];
1280  $s = $matches[3];
1281  }
1282  $this->end_date = sprintf('%04d%02d%02d%02d%02d%02d', $y, $m, $d, $h, $i, $s);
1283  }
1284 
1285  // Gets the learners evaluation access
1286  public function getEvaluationAccess(): string
1287  {
1288  return $this->evaluation_access;
1289  }
1290 
1291  public function setEvaluationAccess(
1292  string $evaluation_access = self::EVALUATION_ACCESS_OFF
1293  ): void {
1294  $this->evaluation_access = $evaluation_access;
1295  }
1296 
1297  public function setActivationVisibility(
1298  bool $a_value
1299  ): void {
1300  $this->activation_visibility = $a_value;
1301  }
1302 
1303  public function getActivationVisibility(): bool
1304  {
1306  }
1307 
1308  public function isActivationLimited(): bool
1309  {
1311  }
1312 
1313  public function setActivationLimited(bool $a_value): void
1314  {
1315  $this->activation_limited = $a_value;
1316  }
1317 
1318  public function getIntroduction(): string
1319  {
1320  return $this->introduction;
1321  }
1322 
1323  public function getOutro(): string
1324  {
1325  return $this->outro;
1326  }
1327 
1332  public function getExistingQuestions(): array
1333  {
1334  $ilDB = $this->db;
1335  $existing_questions = array();
1336  $result = $ilDB->queryF(
1337  "SELECT svy_question.original_id FROM svy_question, svy_svy_qst WHERE " .
1338  "svy_svy_qst.survey_fi = %s AND svy_svy_qst.question_fi = svy_question.question_id",
1339  array('integer'),
1340  array($this->getSurveyId())
1341  );
1342  while ($data = $ilDB->fetchAssoc($result)) {
1343  if ($data["original_id"]) {
1344  $existing_questions[] = (int) $data["original_id"];
1345  }
1346  }
1347  return $existing_questions;
1348  }
1349 
1354  public function getQuestionpoolTitles(
1355  bool $could_be_offline = false,
1356  bool $showPath = false
1357  ): array {
1358  return ilObjSurveyQuestionPool::_getAvailableQuestionpools(true, $could_be_offline, $showPath);
1359  }
1360 
1368  public function moveQuestions(
1369  array $move_questions,
1370  int $target_index,
1371  int $insert_mode
1372  ): void {
1373  $array_pos = array_search($target_index, $this->questions);
1374  $part1 = $part2 = [];
1375  if ($insert_mode === 0) {
1376  $part1 = array_slice($this->questions, 0, $array_pos);
1377  $part2 = array_slice($this->questions, $array_pos);
1378  } elseif ($insert_mode === 1) {
1379  $part1 = array_slice($this->questions, 0, $array_pos + 1);
1380  $part2 = array_slice($this->questions, $array_pos + 1);
1381  }
1382  $found = 0;
1383  foreach ($move_questions as $question_id) {
1384  if (!(!in_array($question_id, $part1))) {
1385  unset($part1[array_search($question_id, $part1)]);
1386  $found++;
1387  }
1388  if (!(!in_array($question_id, $part2))) {
1389  unset($part2[array_search($question_id, $part2)]);
1390  $found++;
1391  }
1392  }
1393  // sanity check: do not move questions if they have not be found in the array
1394  if ($found !== count($move_questions)) {
1395  return;
1396  }
1397  $part1 = array_values($part1);
1398  $part2 = array_values($part2);
1399  $this->questions = array_values(array_merge($part1, $move_questions, $part2));
1400  foreach ($move_questions as $question_id) {
1401  $constraints = $this->getConstraints($question_id);
1402  foreach ($constraints as $idx => $constraint) {
1403  foreach ($part2 as $next_question_id) {
1404  if ($constraint["question"] == $next_question_id) {
1405  // constraint concerning a question that follows -> delete constraint
1406  $this->deleteConstraint($constraint["id"]);
1407  }
1408  }
1409  }
1410  }
1411  $this->saveQuestionsToDb();
1412  }
1413 
1417  public function removeQuestion(
1418  int $question_id
1419  ): void {
1420  $question = self::_instanciateQuestion($question_id);
1421  #20610 if no question found, do nothing.
1422  if ($question) {
1423  $question->delete($question_id);
1424  $this->removeConstraintsConcerningQuestion($question_id);
1425  }
1426  }
1427 
1433  int $question_id
1434  ): void {
1435  $ilDB = $this->db;
1436  $result = $ilDB->queryF(
1437  "SELECT constraint_fi FROM svy_qst_constraint WHERE question_fi = %s AND survey_fi = %s",
1438  array('integer','integer'),
1439  array($question_id, $this->getSurveyId())
1440  );
1441  if ($result->numRows() > 0) {
1442  $remove_constraints = array();
1443  while ($row = $ilDB->fetchAssoc($result)) {
1444  $remove_constraints[] = $row["constraint_fi"];
1445  }
1446  $affectedRows = $ilDB->manipulateF(
1447  "DELETE FROM svy_qst_constraint WHERE question_fi = %s AND survey_fi = %s",
1448  array('integer','integer'),
1449  array($question_id, $this->getSurveyId())
1450  );
1451  foreach ($remove_constraints as $key => $constraint_id) {
1452  $affectedRows = $ilDB->manipulateF(
1453  "DELETE FROM svy_constraint WHERE constraint_id = %s",
1454  array('integer'),
1455  array($constraint_id)
1456  );
1457  }
1458  }
1459  }
1460 
1466  public function removeQuestions(
1467  array $remove_questions,
1468  array $remove_questionblocks
1469  ): void {
1470  $ilDB = $this->db;
1471 
1472  $block_sizes = array();
1473  foreach ($this->getSurveyQuestions() as $question_id => $data) {
1474  if (in_array($question_id, $remove_questions) or in_array($data["questionblock_id"], $remove_questionblocks)) {
1475  unset($this->questions[array_search($question_id, $this->questions)]);
1476  $this->removeQuestion($question_id);
1477  } elseif ($data["questionblock_id"]) {
1478  $block_sizes[$data["questionblock_id"]] = ($block_sizes[$data["questionblock_id"]] ?? 0) + 1;
1479  }
1480  }
1481 
1482  // blocks with just 1 question need to be deleted
1483  foreach ($block_sizes as $block_id => $size) {
1484  if ($size < 2) {
1485  $remove_questionblocks[] = $block_id;
1486  }
1487  }
1488 
1489  foreach (array_unique($remove_questionblocks) as $questionblock_id) {
1490  $affectedRows = $ilDB->manipulateF(
1491  "DELETE FROM svy_qblk WHERE questionblock_id = %s",
1492  array('integer'),
1493  array($questionblock_id)
1494  );
1495  $affectedRows = $ilDB->manipulateF(
1496  "DELETE FROM svy_qblk_qst WHERE questionblock_fi = %s AND survey_fi = %s",
1497  array('integer','integer'),
1498  array($questionblock_id, $this->getSurveyId())
1499  );
1500  }
1501 
1502  $this->questions = array_values($this->questions);
1503  $this->saveQuestionsToDb();
1504  }
1505 
1510  public function unfoldQuestionblocks(
1511  array $questionblocks
1512  ): void {
1513  $ilDB = $this->db;
1514  foreach ($questionblocks as $index) {
1515  $affectedRows = $ilDB->manipulateF(
1516  "DELETE FROM svy_qblk WHERE questionblock_id = %s",
1517  array('integer'),
1518  array($index)
1519  );
1520  $affectedRows = $ilDB->manipulateF(
1521  "DELETE FROM svy_qblk_qst WHERE questionblock_fi = %s AND survey_fi = %s",
1522  array('integer','integer'),
1523  array($index, $this->getSurveyId())
1524  );
1525  }
1526  }
1527 
1531  public function removeQuestionFromBlock(
1532  int $question_id,
1533  int $questionblock_id
1534  ): void {
1535  $ilDB = $this->db;
1536 
1537  $ilDB->manipulateF(
1538  "DELETE FROM svy_qblk_qst WHERE questionblock_fi = %s AND survey_fi = %s AND question_fi = %s",
1539  array('integer','integer','integer'),
1540  array($questionblock_id, $this->getSurveyId(), $question_id)
1541  );
1542  }
1543 
1547  public function addQuestionToBlock(
1548  int $question_id,
1549  int $questionblock_id
1550  ): void {
1551  $ilDB = $this->db;
1552 
1553  // see #22018
1554  if (!$this->isQuestionInAnyBlock($question_id)) {
1555  $next_id = $ilDB->nextId('svy_qblk_qst');
1556  $affectedRows = $ilDB->manipulateF(
1557  "INSERT INTO svy_qblk_qst (qblk_qst_id, survey_fi, questionblock_fi, " .
1558  "question_fi) VALUES (%s, %s, %s, %s)",
1559  array('integer', 'integer', 'integer', 'integer'),
1560  array($next_id, $this->getSurveyId(), $questionblock_id, $question_id)
1561  );
1562 
1563  $this->deleteConstraints($question_id); // #13713
1564  }
1565  }
1566 
1570  public function isQuestionInAnyBlock(
1571  int $a_question_fi
1572  ): bool {
1573  global $DIC;
1574 
1575  $ilDB = $DIC->database();
1576 
1577  $set = $ilDB->query("SELECT * FROM svy_qblk_qst " .
1578  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
1579  " AND question_fi = " . $ilDB->quote($a_question_fi, "integer"));
1580  if ($rec = $ilDB->fetchAssoc($set)) {
1581  return true;
1582  }
1583  return false;
1584  }
1585 
1586 
1592  public function getQuestionblockQuestions(
1593  int $questionblock_id
1594  ): array {
1595  $ilDB = $this->db;
1596  $titles = array();
1597  $result = $ilDB->queryF(
1598  "SELECT svy_question.title, svy_qblk_qst.question_fi, svy_qblk_qst.survey_fi FROM " .
1599  "svy_qblk, svy_qblk_qst, svy_question WHERE svy_qblk.questionblock_id = svy_qblk_qst.questionblock_fi AND " .
1600  "svy_question.question_id = svy_qblk_qst.question_fi AND svy_qblk.questionblock_id = %s",
1601  array('integer'),
1602  array($questionblock_id)
1603  );
1604  $survey_id = "";
1605  while ($row = $ilDB->fetchAssoc($result)) {
1606  $titles[$row["question_fi"]] = $row["title"];
1607  $survey_id = $row["survey_fi"];
1608  }
1609  $result = $ilDB->queryF(
1610  "SELECT question_fi, sequence FROM svy_svy_qst WHERE survey_fi = %s ORDER BY sequence",
1611  array('integer'),
1612  array($survey_id)
1613  );
1614  $resultarray = array();
1615  $counter = 1;
1616  while ($row = $ilDB->fetchAssoc($result)) {
1617  if (array_key_exists($row["question_fi"], $titles)) {
1618  $resultarray[$counter++] = $titles[$row["question_fi"]];
1619  }
1620  }
1621  return $resultarray;
1622  }
1623 
1630  int $questionblock_id
1631  ): array {
1632  $ilDB = $this->db;
1633 
1634  // we need a correct order here, see #22011
1635  $result = $ilDB->queryF(
1636  "SELECT a.question_fi FROM svy_qblk_qst a JOIN svy_svy_qst b ON (a.question_fi = b.question_fi) " .
1637  " WHERE a.questionblock_fi = %s ORDER BY b.sequence",
1638  array("integer"),
1639  array($questionblock_id)
1640  );
1641  $ids = array();
1642  if ($result->numRows()) {
1643  while ($data = $ilDB->fetchAssoc($result)) {
1644  if (!in_array($data['question_fi'], $ids)) { // no duplicates, see #22018
1645  $ids[] = (int) $data['question_fi'];
1646  }
1647  }
1648  }
1649  return $ids;
1650  }
1651 
1656  public static function _getQuestionblock(
1657  int $questionblock_id
1658  ): array {
1659  global $DIC;
1660 
1661  $ilDB = $DIC->database();
1662  $result = $ilDB->queryF(
1663  "SELECT * FROM svy_qblk WHERE questionblock_id = %s",
1664  array('integer'),
1665  array($questionblock_id)
1666  );
1667  $row = $ilDB->fetchAssoc($result);
1668  return $row;
1669  }
1670 
1674  public static function _addQuestionblock(
1675  string $title = "",
1676  int $owner = 0,
1677  bool $show_questiontext = true,
1678  bool $show_blocktitle = false,
1679  bool $compress_view = false
1680  ): int {
1681  global $DIC;
1682 
1683  $ilDB = $DIC->database();
1684  $next_id = $ilDB->nextId('svy_qblk');
1685  $ilDB->manipulateF(
1686  "INSERT INTO svy_qblk (questionblock_id, title, show_questiontext," .
1687  " show_blocktitle, owner_fi, tstamp, compress_view) " .
1688  "VALUES (%s, %s, %s, %s, %s, %s, %s)",
1689  array('integer','text','integer','integer','integer','integer','integer'),
1690  array($next_id, $title, $show_questiontext, $show_blocktitle, $owner, time(),$compress_view)
1691  );
1692  return $next_id;
1693  }
1694 
1698  public function createQuestionblock(
1699  string $title,
1700  bool $show_questiontext,
1701  bool $show_blocktitle,
1702  array $questions,
1703  bool $compress_view = false
1704  ): void {
1705  $ilDB = $this->db;
1706 
1707  // if the selected questions are not in a continous selection, move all questions of the
1708  // questionblock at the position of the first selected question
1709  $this->moveQuestions($questions, $questions[0], 0);
1710 
1711  // now save the question block
1712  $ilUser = $this->user;
1713  $next_id = $ilDB->nextId('svy_qblk');
1714  $affectedRows = $ilDB->manipulateF(
1715  "INSERT INTO svy_qblk (questionblock_id, title, show_questiontext," .
1716  " show_blocktitle, owner_fi, tstamp, compress_view) VALUES (%s, %s, %s, %s, %s, %s, %s)",
1717  array('integer','text','text','text','integer','integer','integer'),
1718  array($next_id, $title, $show_questiontext, $show_blocktitle, $ilUser->getId(), time(), $compress_view)
1719  );
1720  if ($affectedRows) {
1721  $questionblock_id = $next_id;
1722  foreach ($questions as $index) {
1723  if (!$this->isQuestionInAnyBlock($index)) {
1724  $next_id = $ilDB->nextId('svy_qblk_qst'); // #22018
1725  $affectedRows = $ilDB->manipulateF(
1726  "INSERT INTO svy_qblk_qst (qblk_qst_id, survey_fi, questionblock_fi, " .
1727  "question_fi) VALUES (%s, %s, %s, %s)",
1728  array('integer', 'integer', 'integer', 'integer'),
1729  array($next_id, $this->getSurveyId(), $questionblock_id, $index)
1730  );
1731  $this->deleteConstraints($index);
1732  }
1733  }
1734  }
1735  }
1736 
1740  public function modifyQuestionblock(
1741  int $questionblock_id,
1742  string $title,
1743  bool $show_questiontext,
1744  bool $show_blocktitle,
1745  bool $compress_view = false
1746  ): void {
1747  $ilDB = $this->db;
1748  $ilDB->manipulateF(
1749  "UPDATE svy_qblk SET title = %s, show_questiontext = %s," .
1750  " show_blocktitle = %s, compress_view = %s WHERE questionblock_id = %s",
1751  array('text','text','text','integer', 'integer'),
1752  array($title, $show_questiontext, $show_blocktitle, $compress_view, $questionblock_id)
1753  );
1754  }
1755 
1760  public function deleteConstraints(
1761  int $question_id
1762  ): void {
1763  $ilDB = $this->db;
1764  $result = $ilDB->queryF(
1765  "SELECT constraint_fi FROM svy_qst_constraint WHERE question_fi = %s AND survey_fi = %s",
1766  array('integer','integer'),
1767  array($question_id, $this->getSurveyId())
1768  );
1769  $constraints = array();
1770  while ($row = $ilDB->fetchAssoc($result)) {
1771  $constraints[] = $row["constraint_fi"];
1772  }
1773  foreach ($constraints as $constraint_id) {
1774  $this->deleteConstraint($constraint_id);
1775  }
1776  }
1777 
1782  public function deleteConstraint(
1783  int $constraint_id
1784  ): void {
1785  $ilDB = $this->db;
1786  $affectedRows = $ilDB->manipulateF(
1787  "DELETE FROM svy_constraint WHERE constraint_id = %s",
1788  array('integer'),
1789  array($constraint_id)
1790  );
1791  $affectedRows = $ilDB->manipulateF(
1792  "DELETE FROM svy_qst_constraint WHERE constraint_fi = %s",
1793  array('integer'),
1794  array($constraint_id)
1795  );
1796  }
1797 
1802  public function getSurveyQuestions(
1803  bool $with_answers = false
1804  ): array {
1805  $ilDB = $this->db;
1806  // get questionblocks
1807  $all_questions = array();
1808  $result = $ilDB->queryF(
1809  "SELECT svy_qtype.type_tag, svy_qtype.plugin, svy_question.question_id, " .
1810  "svy_svy_qst.heading FROM svy_qtype, svy_question, svy_svy_qst WHERE svy_svy_qst.survey_fi = %s AND " .
1811  "svy_svy_qst.question_fi = svy_question.question_id AND svy_question.questiontype_fi = svy_qtype.questiontype_id " .
1812  "ORDER BY svy_svy_qst.sequence",
1813  array('integer'),
1814  array($this->getSurveyId())
1815  );
1816  while ($row = $ilDB->fetchAssoc($result)) {
1817  $add = true;
1818  if ($row["plugin"]) {
1819  $add = false;
1820  }
1821  if ($add) {
1822  $question = self::_instanciateQuestion($row["question_id"]);
1823  $questionrow = $question->getQuestionDataArray($row["question_id"]);
1824  foreach ($row as $key => $value) {
1825  $questionrow[$key] = $value;
1826  }
1827  $all_questions[$row["question_id"]] = $questionrow;
1828  $all_questions[$row["question_id"]]["usableForPrecondition"] = $question->usableForPrecondition();
1829  $all_questions[$row["question_id"]]["availableRelations"] = $question->getAvailableRelations();
1830  }
1831  }
1832  // get all questionblocks
1833  $questionblocks = array();
1834  if (count($all_questions)) {
1835  $result = $ilDB->queryF(
1836  "SELECT svy_qblk.*, svy_qblk_qst.question_fi FROM svy_qblk, svy_qblk_qst WHERE " .
1837  "svy_qblk.questionblock_id = svy_qblk_qst.questionblock_fi AND svy_qblk_qst.survey_fi = %s " .
1838  "AND " . $ilDB->in('svy_qblk_qst.question_fi', array_keys($all_questions), false, 'integer'),
1839  array('integer'),
1840  array($this->getSurveyId())
1841  );
1842  while ($row = $ilDB->fetchAssoc($result)) {
1843  $questionblocks[$row['question_fi']] = $row;
1844  }
1845  }
1846 
1847  foreach ($all_questions as $question_id => $row) {
1848  $constraints = $this->getConstraints($question_id);
1849  if (isset($questionblocks[$question_id])) {
1850  $all_questions[$question_id]["questionblock_title"] = $questionblocks[$question_id]['title'];
1851  $all_questions[$question_id]["questionblock_id"] = $questionblocks[$question_id]['questionblock_id'];
1852  } else {
1853  $all_questions[$question_id]["questionblock_title"] = "";
1854  $all_questions[$question_id]["questionblock_id"] = "";
1855  }
1856  $all_questions[$question_id]["constraints"] = $constraints;
1857  if ($with_answers) {
1858  $answers = array();
1859  $result = $ilDB->queryF(
1860  "SELECT svy_variable.*, svy_category.title FROM svy_variable, svy_category " .
1861  "WHERE svy_variable.question_fi = %s AND svy_variable.category_fi = svy_category.category_id " .
1862  "ORDER BY sequence ASC",
1863  array('integer'),
1864  array($question_id)
1865  );
1866  if ($result->numRows() > 0) {
1867  while ($data = $ilDB->fetchAssoc($result)) {
1868  $answers[] = $data["title"];
1869  }
1870  }
1871  $all_questions[$question_id]["answers"] = $answers;
1872  }
1873  }
1874  return $all_questions;
1875  }
1876 
1882  public function setObligatoryStates(
1883  array $obligatory_questions
1884  ): void {
1885  $ilDB = $this->db;
1886  $result = $ilDB->queryF(
1887  "SELECT * FROM svy_svy_qst WHERE survey_fi = %s",
1888  array('integer'),
1889  array($this->getSurveyId())
1890  );
1891  if ($result->numRows()) {
1892  while ($row = $ilDB->fetchAssoc($result)) {
1893  if (!array_key_exists($row["question_fi"], $obligatory_questions)) {
1894  $obligatory_questions[$row["question_fi"]] = 0;
1895  }
1896  }
1897  }
1898  // set the obligatory states in the database
1899  foreach ($obligatory_questions as $question_fi => $obligatory) {
1900  // #12420
1901  $ilDB->manipulate("UPDATE svy_question" .
1902  " SET obligatory = " . $ilDB->quote($obligatory, "integer") .
1903  " WHERE question_id = " . $ilDB->quote($question_fi, "integer"));
1904  }
1905  }
1906 
1911  public function getSurveyPages(): array
1912  {
1913  $ilDB = $this->db;
1914  // get questionblocks
1915  $all_questions = array();
1916  $result = $ilDB->queryF(
1917  "SELECT svy_question.*, svy_qtype.type_tag, svy_svy_qst.heading FROM " .
1918  "svy_question, svy_qtype, svy_svy_qst WHERE svy_svy_qst.survey_fi = %s AND " .
1919  "svy_svy_qst.question_fi = svy_question.question_id AND svy_question.questiontype_fi = svy_qtype.questiontype_id " .
1920  "ORDER BY svy_svy_qst.sequence",
1921  array('integer'),
1922  array($this->getSurveyId())
1923  );
1924  while ($row = $ilDB->fetchAssoc($result)) {
1925  $all_questions[$row["question_id"]] = $row;
1926  }
1927  // get all questionblocks
1928  $questionblocks = array();
1929  if (count($all_questions)) {
1930  $result = $ilDB->queryF(
1931  "SELECT svy_qblk.*, svy_qblk_qst.question_fi FROM svy_qblk, svy_qblk_qst " .
1932  "WHERE svy_qblk.questionblock_id = svy_qblk_qst.questionblock_fi AND svy_qblk_qst.survey_fi = %s " .
1933  "AND " . $ilDB->in('svy_qblk_qst.question_fi', array_keys($all_questions), false, 'integer'),
1934  array('integer'),
1935  array($this->getSurveyId())
1936  );
1937  while ($row = $ilDB->fetchAssoc($result)) {
1938  $questionblocks[$row['question_fi']] = $row;
1939  }
1940  }
1941 
1942  $all_pages = array();
1943  $pageindex = -1;
1944  $currentblock = "";
1945  foreach ($all_questions as $question_id => $row) {
1946  $constraints = array();
1947  if (isset($questionblocks[$question_id])) {
1948  if (!$currentblock or ($currentblock != $questionblocks[$question_id]['questionblock_id'])) {
1949  $pageindex++;
1950  }
1951  $all_questions[$question_id]['page'] = $pageindex;
1952  $all_questions[$question_id]["questionblock_title"] = $questionblocks[$question_id]['title'];
1953  $all_questions[$question_id]["questionblock_id"] = $questionblocks[$question_id]['questionblock_id'];
1954  $all_questions[$question_id]["questionblock_show_questiontext"] = $questionblocks[$question_id]['show_questiontext'];
1955  $all_questions[$question_id]["questionblock_show_blocktitle"] = $questionblocks[$question_id]['show_blocktitle'];
1956  $all_questions[$question_id]["questionblock_compress_view"] = $questionblocks[$question_id]['compress_view'];
1957  $currentblock = $questionblocks[$question_id]['questionblock_id'];
1958  } else {
1959  $pageindex++;
1960  $all_questions[$question_id]['page'] = $pageindex;
1961  $all_questions[$question_id]["questionblock_title"] = "";
1962  $all_questions[$question_id]["questionblock_id"] = "";
1963  $all_questions[$question_id]["questionblock_show_questiontext"] = 1;
1964  $all_questions[$question_id]["questionblock_show_blocktitle"] = 1;
1965  $all_questions[$question_id]["questionblock_compress_view"] = false;
1966  $currentblock = "";
1967  }
1968  $constraints = $this->getConstraints($question_id);
1969  $all_questions[$question_id]["constraints"] = $constraints;
1970  if (!isset($all_pages[$pageindex])) {
1971  $all_pages[$pageindex] = array();
1972  }
1973  $all_pages[$pageindex][] = $all_questions[$question_id];
1974  }
1975  // calculate position percentage for every page
1976  $max = count($all_pages);
1977  $counter = 1;
1978  foreach ($all_pages as $index => $block) {
1979  foreach ($block as $blockindex => $question) {
1980  $all_pages[$index][$blockindex]["position"] = $counter / $max;
1981  }
1982  $counter++;
1983  }
1984 
1985  return $all_pages;
1986  }
1987 
1996  public function getNextPage(
1997  int $active_page_question_id,
1998  int $direction
1999  ): ?array {
2000  $foundpage = -1;
2001  $pages = $this->getSurveyPages();
2002  if ($active_page_question_id === 0) {
2003  return $pages[0];
2004  }
2005  foreach ($pages as $key => $question_array) {
2006  foreach ($question_array as $question) {
2007  if ($active_page_question_id == $question["question_id"]) {
2008  $foundpage = $key;
2009  }
2010  }
2011  }
2012  if ($foundpage === -1) {
2013  throw new ilSurveyException("nextPage: Current page not found.");
2014  } else {
2015  $foundpage += $direction;
2016  if ($foundpage < 0) {
2017  return null;
2018  }
2019  if ($foundpage >= count($pages)) {
2020  return null;
2021  }
2022  return $pages[$foundpage];
2023  }
2024  }
2025 
2029  public function getAvailableQuestionpools(
2030  bool $use_obj_id = false,
2031  bool $could_be_offline = false,
2032  bool $showPath = false,
2033  string $permission = "read"
2034  ): array {
2035  return ilObjSurveyQuestionPool::_getAvailableQuestionpools($use_obj_id, $could_be_offline, $showPath, $permission);
2036  }
2037 
2042  public function getPrecondition(
2043  int $constraint_id
2044  ): array {
2045  $ilDB = $this->db;
2046 
2047  $result = $ilDB->queryF(
2048  "SELECT svy_constraint.*, svy_relation.*, svy_qst_constraint.question_fi ref_question_fi FROM svy_qst_constraint, svy_constraint, " .
2049  "svy_relation WHERE svy_constraint.relation_fi = svy_relation.relation_id AND " .
2050  "svy_qst_constraint.constraint_fi = svy_constraint.constraint_id AND svy_constraint.constraint_id = %s",
2051  array('integer'),
2052  array($constraint_id)
2053  );
2054  $pc = array();
2055  if ($result->numRows()) {
2056  $pc = $ilDB->fetchAssoc($result);
2057  }
2058  return $pc;
2059  }
2060 
2065  public function getConstraints(
2066  int $question_id
2067  ): array {
2068  $ilDB = $this->db;
2069 
2070  $result_array = array();
2071  $result = $ilDB->queryF(
2072  "SELECT svy_constraint.*, svy_relation.* FROM svy_qst_constraint, svy_constraint, svy_relation " .
2073  "WHERE svy_constraint.relation_fi = svy_relation.relation_id AND " .
2074  "svy_qst_constraint.constraint_fi = svy_constraint.constraint_id AND svy_qst_constraint.question_fi = %s " .
2075  "AND svy_qst_constraint.survey_fi = %s",
2076  array('integer','integer'),
2077  array($question_id, $this->getSurveyId())
2078  );
2079  while ($row = $ilDB->fetchAssoc($result)) {
2080  $question_type = SurveyQuestion::_getQuestionType($row["question_fi"]);
2081  SurveyQuestion::_includeClass($question_type);
2082  $question = new $question_type();
2083  $question->loadFromDb($row["question_fi"]);
2084  $valueoutput = $question->getPreconditionValueOutput($row["value"]);
2085  $result_array[] = array("id" => $row["constraint_id"],
2086  "question" => $row["question_fi"],
2087  "short" => $row["shortname"],
2088  "long" => $row["longname"],
2089  "value" => $row["value"],
2090  "conjunction" => $row["conjunction"],
2091  "valueoutput" => $valueoutput
2092  );
2093  }
2094  return $result_array;
2095  }
2096 
2100  public static function _getConstraints(
2101  int $survey_id
2102  ): array {
2103  global $DIC;
2104 
2105  $ilDB = $DIC->database();
2106  $result_array = array();
2107  $result = $ilDB->queryF(
2108  "SELECT svy_qst_constraint.question_fi as for_question, svy_constraint.*, svy_relation.* " .
2109  "FROM svy_qst_constraint, svy_constraint, svy_relation WHERE svy_constraint.relation_fi = svy_relation.relation_id " .
2110  "AND svy_qst_constraint.constraint_fi = svy_constraint.constraint_id AND svy_qst_constraint.survey_fi = %s",
2111  array('integer'),
2112  array($survey_id)
2113  );
2114  while ($row = $ilDB->fetchAssoc($result)) {
2115  $result_array[] = array("id" => $row["constraint_id"],
2116  "for_question" => $row["for_question"],
2117  "question" => $row["question_fi"],
2118  "short" => $row["shortname"],
2119  "long" => $row["longname"],
2120  "relation_id" => $row["relation_id"],
2121  "value" => $row["value"],
2122  'conjunction' => $row['conjunction']
2123  );
2124  }
2125  return $result_array;
2126  }
2127 
2128 
2133  public function getVariables(
2134  int $question_id
2135  ): array {
2136  $ilDB = $this->db;
2137 
2138  $result_array = array();
2139  $result = $ilDB->queryF(
2140  "SELECT svy_variable.*, svy_category.title FROM svy_variable LEFT JOIN " .
2141  "svy_category ON svy_variable.category_fi = svy_category.category_id WHERE svy_variable.question_fi = %s " .
2142  "ORDER BY svy_variable.sequence",
2143  array('integer'),
2144  array($question_id)
2145  );
2146  while ($row = $ilDB->fetchObject($result)) {
2147  $result_array[$row->sequence] = $row;
2148  }
2149  return $result_array;
2150  }
2151 
2161  public function addConstraint(
2162  int $if_question_id,
2163  int $relation,
2164  float $value,
2165  int $conjunction
2166  ): ?int {
2167  $ilDB = $this->db;
2168 
2169  $next_id = $ilDB->nextId('svy_constraint');
2170  $affectedRows = $ilDB->manipulateF(
2171  "INSERT INTO svy_constraint (constraint_id, question_fi, relation_fi, value, conjunction) VALUES " .
2172  "(%s, %s, %s, %s, %s)",
2173  array('integer','integer','integer','float', 'integer'),
2174  array($next_id, $if_question_id, $relation, $value, $conjunction)
2175  );
2176  if ($affectedRows) {
2177  return $next_id;
2178  } else {
2179  return null;
2180  }
2181  }
2182 
2183 
2187  public function addConstraintToQuestion(
2188  int $to_question_id,
2189  int $constraint_id
2190  ): void {
2191  $ilDB = $this->db;
2192 
2193  $next_id = $ilDB->nextId('svy_qst_constraint');
2194  $ilDB->manipulateF(
2195  "INSERT INTO svy_qst_constraint (question_constraint_id, survey_fi, question_fi, " .
2196  "constraint_fi) VALUES (%s, %s, %s, %s)",
2197  array('integer','integer','integer','integer'),
2198  array($next_id, $this->getSurveyId(), $to_question_id, $constraint_id)
2199  );
2200  }
2201 
2205  public function updateConstraint(
2206  int $precondition_id,
2207  int $if_question_id,
2208  int $relation,
2209  float $value,
2210  int $conjunction
2211  ): void {
2212  $ilDB = $this->db;
2213  $ilDB->manipulateF(
2214  "UPDATE svy_constraint SET question_fi = %s, relation_fi = %s, value = %s, conjunction = %s " .
2215  "WHERE constraint_id = %s",
2216  array('integer','integer','float','integer','integer'),
2217  array($if_question_id, $relation, $value, $conjunction, $precondition_id)
2218  );
2219  }
2220 
2225  array $questions,
2226  int $conjunction
2227  ): void {
2228  $ilDB = $this->db;
2229  foreach ($questions as $question_id) {
2230  $ilDB->manipulateF(
2231  "UPDATE svy_constraint SET conjunction = %s " .
2232  "WHERE constraint_id IN (SELECT constraint_fi FROM svy_qst_constraint WHERE svy_qst_constraint.question_fi = %s)",
2233  array('integer','integer'),
2234  array($conjunction, $question_id)
2235  );
2236  }
2237  }
2238 
2242  public function getAllRelations(
2243  bool $short_as_key = false
2244  ): array {
2245  $ilDB = $this->db;
2246 
2247  // #7987
2248  $custom_order = array("equal", "not_equal", "less", "less_or_equal", "more", "more_or_equal");
2249  $custom_order = array_flip($custom_order);
2250 
2251  $result_array = array();
2252  $result = $ilDB->query("SELECT * FROM svy_relation");
2253  while ($row = $ilDB->fetchAssoc($result)) {
2254  if ($short_as_key) {
2255  $result_array[$row["shortname"]] = array("short" => $row["shortname"], "long" => $row["longname"], "id" => $row["relation_id"], "order" => $custom_order[$row["longname"]]);
2256  } else {
2257  $result_array[$row["relation_id"]] = array("short" => $row["shortname"], "long" => $row["longname"], "order" => $custom_order[$row["longname"]]);
2258  }
2259  }
2260 
2261  $result_array = ilArrayUtil::sortArray($result_array, "order", "ASC", true, true);
2262  foreach ($result_array as $idx => $item) {
2263  unset($result_array[$idx]["order"]);
2264  }
2265 
2266  return $result_array;
2267  }
2268 
2269 
2270 
2271 
2276  public function deleteWorkingData(
2277  int $question_id,
2278  int $active_id
2279  ): void {
2280  $ilDB = $this->db;
2281 
2282  $affectedRows = $ilDB->manipulateF(
2283  "DELETE FROM svy_answer WHERE question_fi = %s AND active_fi = %s",
2284  array('integer','integer'),
2285  array($question_id, $active_id)
2286  );
2287  }
2288 
2293  public function loadWorkingData(
2294  int $question_id,
2295  int $active_id
2296  ): array {
2297  $ilDB = $this->db;
2298  $result_array = array();
2299  $result = $ilDB->queryF(
2300  "SELECT * FROM svy_answer WHERE question_fi = %s AND active_fi = %s",
2301  array('integer','integer'),
2302  array($question_id, $active_id)
2303  );
2304  if ($result->numRows() >= 1) {
2305  while ($row = $ilDB->fetchAssoc($result)) {
2306  $result_array[] = $row;
2307  }
2308  }
2309  return $result_array;
2310  }
2311 
2316  public function finishSurvey(
2317  int $finished_id,
2318  int $appr_id = 0
2319  ): void {
2320  $ilDB = $this->db;
2321 
2322  $ilDB->manipulateF(
2323  "UPDATE svy_finished SET state = %s, tstamp = %s" .
2324  " WHERE survey_fi = %s AND finished_id = %s",
2325  array('text','integer','integer','integer'),
2326  array(1, time(), $this->getSurveyId(), $finished_id)
2327  );
2328 
2329  // self eval writes skills on finishing
2330  if ($this->getMode() === self::MODE_SELF_EVAL) {
2331  $user = $this->getUserDataFromActiveId($finished_id);
2332  $sskill = new ilSurveySkill($this);
2333  $sskill->writeAndAddSelfEvalSkills($user['usr_id']);
2334  }
2335 
2336  // self eval writes skills on finishing
2337  if ($this->getMode() === self::MODE_IND_FEEDB) {
2338  // we use a rater id like "a27" for anonymous or
2339  // "123" for non anonymous user
2340  // @todo: move this e.g. to participant manager
2341  $raters = $this->getRatersData($appr_id);
2342  $run_manager = $this->survey_service
2343  ->domain()
2344  ->execution()
2345  ->run($this, $this->user->getId(), $appr_id);
2346  $run = $run_manager->getById($finished_id);
2347  $rater_id = "";
2348  if ($run->getUserId() !== 0 && $run->getUserId() !== ANONYMOUS_USER_ID) {
2349  $rater_id = $run->getUserId();
2350  } else {
2351  foreach ($raters as $id => $rater) {
2352  if (($rater["code"] ?? "") === $run->getCode()) {
2353  $rater_id = $id;
2354  }
2355  }
2356  }
2357  $sskill = new ilSurveySkill($this);
2358  //$sskill->writeAndAddSelfEvalSkills($user['usr_id']);
2359  $sskill->writeAndAddIndFeedbackSkills($finished_id, $appr_id, $rater_id);
2360  }
2361 
2362  $this->checkTutorNotification();
2363  }
2364 
2371  public function setPage(
2372  int $finished_id,
2373  int $page_id
2374  ): void {
2375  $ilDB = $this->db;
2376 
2377  $ilDB->manipulateF(
2378  "UPDATE svy_finished SET lastpage = %s WHERE finished_id = %s",
2379  array('integer','integer'),
2380  array(($page_id) ?: 0, $finished_id)
2381  );
2382  }
2383 
2390  public function sendNotificationMail(
2391  int $a_user_id,
2392  string $a_anonymize_id,
2393  int $a_appr_id = 0
2394  ): void {
2395  // #12755
2396  $placeholders = array(
2397  "FIRST_NAME" => "firstname",
2398  "LAST_NAME" => "lastname",
2399  "LOGIN" => "login",
2400  // old style
2401  "firstname" => "firstname"
2402  );
2403 
2404  //mailaddresses is just text split by commas.
2405  //sendMail can send emails if it gets an user id or an email as first parameter.
2406  $recipients = explode(",", $this->mailaddresses ?? "");
2407  foreach ($recipients as $recipient) {
2408  // #11298
2409  $ntf = new ilSystemNotification();
2410  $ntf->setLangModules(array("survey"));
2411  $ntf->setRefId($this->getRefId());
2412  $ntf->setSubjectLangId('finished_mail_subject');
2413 
2414  $messagetext = $this->mailparticipantdata;
2415  $data = [];
2416  if (trim($messagetext ?? "")) {
2417  if (!$this->hasAnonymizedResults()) {
2418  $data = ilObjUser::_getUserData(array($a_user_id));
2419  $data = $data[0];
2420  }
2421  foreach ($placeholders as $key => $mapping) {
2422  if ($this->hasAnonymizedResults()) { // #16480
2423  $messagetext = str_replace('[' . $key . ']', '', $messagetext);
2424  } else {
2425  $messagetext = str_replace('[' . $key . ']', trim($data[$mapping] ?? ""), $messagetext);
2426  }
2427  }
2428  $ntf->setIntroductionDirect($messagetext);
2429  } else {
2430  $ntf->setIntroductionLangId('survey_notification_finished_introduction');
2431  }
2432 
2433  // 360°? add appraisee data
2434  if ($a_appr_id) {
2435  $ntf->addAdditionalInfo(
2436  'survey_360_appraisee',
2438  );
2439  }
2440 
2441  $active_id = $this->getActiveID($a_user_id, $a_anonymize_id, $a_appr_id);
2442  if ($active_id) { // 43908
2443  $ntf->addAdditionalInfo(
2444  'results',
2445  $this->getParticipantTextResults($active_id),
2446  true
2447  );
2448  }
2449 
2450  $ntf->setGotoLangId('survey_notification_tutor_link');
2451  $ntf->setReasonLangId('survey_notification_finished_reason');
2452 
2453  $recipient = trim($recipient ?? "");
2454  $user_id = (int) ilObjUser::_lookupId($recipient);
2455  if ($user_id > 0) {
2456  $ntf->sendMailAndReturnRecipients([$user_id]);
2457  } else {
2458  $user_ids = ilObjUser::getUserIdsByEmail($recipient);
2459  if (count($user_ids) > 0) {
2460  $ntf->sendMailAndReturnRecipients([current($user_ids)]);
2461  }
2462  }
2463  /* note: this block is replace by the single line above
2464  since the UI asks for account names and the "e-mail" fallback leads
2465  to strange issues like multiple mails. Also the test case has been
2466  adopted, see https://mantis.ilias.de/view.php?id=36327
2467  if (is_numeric($recipient)) {
2468  $lng = $ntf->getUserLanguage((int) $recipient);
2469  $ntf->sendMailAndReturnRecipients([(int) $recipient]);
2470  } else {
2471  $recipient = trim($recipient);
2472  $user_ids = ilObjUser::getUserIdsByEmail($recipient);
2473  if (empty($user_ids)) {
2474  $ntf->sendMailAndReturnRecipients([ilObjUser::_lookupId($recipient)]);
2475  } else {
2476  foreach ($user_ids as $user_id) {
2477  $lng = $ntf->getUserLanguage($user_id);
2478  $ntf->sendMailAndReturnRecipients(array($user_id));
2479  }
2480  }
2481  }*/
2482  }
2483  }
2484 
2485  protected function getParticipantTextResults(
2486  int $active_id
2487  ): string {
2488  $textresult = "";
2489  $userResults = $this->getUserSpecificResults(array($active_id));
2490  $questions = $this->getSurveyQuestions(true);
2491  $questioncounter = 1;
2492  foreach ($questions as $question_id => $question_data) {
2493  $textresult .= $questioncounter++ . ". " . $question_data["title"] . "\n";
2494  $found = $userResults[$question_id][$active_id];
2495  $text = "";
2496  if (is_array($found)) {
2497  $text = implode("\n", $found);
2498  } else {
2499  $text = $found;
2500  }
2501  if ($text === '') {
2502  $text = self::getSurveySkippedValue();
2503  }
2504  $text = str_replace("<br />", "\n", $text);
2505  $textresult .= $text . "\n\n";
2506  }
2507  return $textresult;
2508  }
2509 
2514  public function getActiveID(
2515  int $user_id,
2516  string $anonymize_id,
2517  int $appr_id
2518  ): ?int {
2519  $ilDB = $this->db;
2520 
2521  // #15031 - should not matter if code was used by registered or anonymous (each code must be unique)
2522  if ($anonymize_id) {
2523  $result = $ilDB->queryF(
2524  "SELECT finished_id FROM svy_finished" .
2525  " WHERE survey_fi = %s AND anonymous_id = %s AND appr_id = %s",
2526  array('integer','text','integer'),
2527  array($this->getSurveyId(), $anonymize_id, $appr_id)
2528  );
2529  } else {
2530  $result = $ilDB->queryF(
2531  "SELECT finished_id FROM svy_finished" .
2532  " WHERE survey_fi = %s AND user_fi = %s AND appr_id = %s",
2533  array('integer','integer','integer'),
2534  array($this->getSurveyId(), $user_id, $appr_id)
2535  );
2536  }
2537  if ($result->numRows() === 0) {
2538  return null;
2539  } else {
2540  $row = $ilDB->fetchAssoc($result);
2541  return (int) $row["finished_id"];
2542  }
2543  }
2544 
2549  public function getLastActivePage(
2550  int $active_id
2551  ): ?int {
2552  $ilDB = $this->db;
2553  $result = $ilDB->queryF(
2554  "SELECT lastpage FROM svy_finished WHERE finished_id = %s",
2555  array('integer'),
2556  array($active_id)
2557  );
2558  if ($row = $ilDB->fetchAssoc($result)) {
2559  return (int) $row["lastpage"];
2560  }
2561  return null;
2562  }
2563 
2572  public function checkConstraint(
2573  array $constraint_data,
2574  ?array $working_data
2575  ): bool {
2576  if (!is_array($working_data) || count($working_data) === 0) {
2577  return 0;
2578  }
2579 
2580  if ((count($working_data) === 1) and (strcmp($working_data[0]["value"], "") === 0)) {
2581  return 0;
2582  }
2583 
2584  $found = false;
2585  foreach ($working_data as $data) {
2586  switch ($constraint_data["short"]) {
2587  case "<":
2588  if ($data["value"] < $constraint_data["value"]) {
2589  $found = true;
2590  }
2591  break;
2592 
2593  case "<=":
2594  if ($data["value"] <= $constraint_data["value"]) {
2595  $found = true;
2596  }
2597  break;
2598 
2599  case "=":
2600  if ($data["value"] == $constraint_data["value"]) {
2601  $found = true;
2602  }
2603  break;
2604 
2605  case "<>":
2606  if ($data["value"] <> $constraint_data["value"]) {
2607  $found = true;
2608  }
2609  break;
2610 
2611  case ">=":
2612  if ($data["value"] >= $constraint_data["value"]) {
2613  $found = true;
2614  }
2615  break;
2616 
2617  case ">":
2618  if ($data["value"] > $constraint_data["value"]) {
2619  $found = true;
2620  }
2621  break;
2622  }
2623  if ($found) {
2624  break;
2625  }
2626  }
2627 
2628  return (int) $found;
2629  }
2630 
2634  public static function _hasDatasets(
2635  int $survey_id
2636  ): bool {
2637  global $DIC;
2638 
2639  $ilDB = $DIC->database();
2640 
2641  $result = $ilDB->queryF(
2642  "SELECT finished_id FROM svy_finished WHERE survey_fi = %s",
2643  array('integer'),
2644  array($survey_id)
2645  );
2646  return (bool) $result->numRows();
2647  }
2648 
2654  public function getSurveyFinishedIds(): array
2655  {
2656  $ilDB = $this->db;
2657 
2658  $users = array();
2659  $result = $ilDB->queryF(
2660  "SELECT * FROM svy_finished WHERE survey_fi = %s",
2661  array('integer'),
2662  array($this->getSurveyId())
2663  );
2664  if ($result->numRows()) {
2665  while ($row = $ilDB->fetchAssoc($result)) {
2666  $users[] = (int) $row["finished_id"];
2667  }
2668  }
2669  return $users;
2670  }
2671 
2677  public function getUserSpecificResults(
2678  array $finished_ids
2679  ): array {
2680  $evaluation = array();
2681 
2682  foreach (array_keys($this->getSurveyQuestions()) as $question_id) {
2683  // get question instance
2684  $question_type = SurveyQuestion::_getQuestionType($question_id);
2685  SurveyQuestion::_includeClass($question_type);
2686  $question = new $question_type();
2687  $question->loadFromDb($question_id);
2688 
2689  $q_eval = SurveyQuestion::_instanciateQuestionEvaluation($question_id, $finished_ids);
2690  $q_res = $q_eval->getResults();
2691 
2692  $data = array();
2693  foreach ($finished_ids as $user_id) {
2694  $data[$user_id] = $q_eval->parseUserSpecificResults($q_res, $user_id);
2695  }
2696 
2697  $evaluation[$question_id] = $data;
2698  }
2699 
2700  return $evaluation;
2701  }
2702 
2707  public function getUserDataFromActiveId(
2708  int $active_id,
2709  bool $force_non_anonymous = false
2710  ): array {
2711  $ilDB = $this->db;
2712 
2713  $surveySetting = new ilSetting("survey");
2714  $use_anonymous_id = $surveySetting->get("use_anonymous_id");
2715  $result = $ilDB->queryF(
2716  "SELECT * FROM svy_finished WHERE finished_id = %s",
2717  array('integer'),
2718  array($active_id)
2719  );
2720  $row = array();
2721  $foundrows = $result->numRows();
2722  if ($foundrows) {
2723  $row = $ilDB->fetchAssoc($result);
2724  }
2725  $name = ($use_anonymous_id) ? $row["anonymous_id"] : $this->lng->txt("anonymous");
2726  $userdata = array(
2727  "fullname" => $name,
2728  "sortname" => $name,
2729  "firstname" => "",
2730  "lastname" => "",
2731  "login" => "",
2732  "gender" => "",
2733  "active_id" => (string) $active_id
2734  );
2735  if ($foundrows) {
2736  if (($row["user_fi"] > 0) &&
2737  (($row["user_fi"] != ANONYMOUS_USER_ID &&
2738  !$this->hasAnonymizedResults() &&
2739  !$this->get360Mode()) || // 360° uses ANONYMIZE_CODE_ALL which is wrong - see ilObjSurveyGUI::afterSave()
2740  $force_non_anonymous)) {
2741  if (ilObjUser::_lookupLogin($row["user_fi"]) === '') {
2742  $userdata["fullname"] = $userdata["sortname"] = $this->lng->txt("deleted_user");
2743  } else {
2744  $user = new ilObjUser($row["user_fi"]);
2745  $userdata['usr_id'] = $row['user_fi'];
2746  $userdata["fullname"] = $user->getFullname();
2747  $gender = $user->getGender();
2748  if (strlen($gender) === 1) {
2749  $gender = $this->lng->txt("gender_$gender");
2750  }
2751  $userdata["gender"] = $gender;
2752  $userdata["firstname"] = $user->getFirstname();
2753  $userdata["lastname"] = $user->getLastname();
2754  $userdata["sortname"] = $user->getLastname() . ", " . $user->getFirstname();
2755  $userdata["login"] = $user->getLogin();
2756  }
2757  }
2758  if ($row["user_fi"] == 0 || $row["user_fi"] == ANONYMOUS_USER_ID) {
2759  $code = $this->code_manager->getByUserKey((string) $row["anonymous_id"]);
2760  if (!is_null($code) && $this->feature_config->usesAppraisees()) {
2761  $userdata["firstname"] = $code->getFirstName();
2762  $userdata["lastname"] = $code->getLastName();
2763  $userdata["sortname"] = $code->getLastName() . ", " . $code->getFirstName();
2764  }
2765  }
2766  }
2767  return $userdata;
2768  }
2769 
2774  public function getEvaluationByUser(
2775  array $questions,
2776  int $active_id
2777  ): array {
2778  $ilDB = $this->db;
2779 
2780  // collect all answers
2781  $answers = array();
2782  $result = $ilDB->queryF(
2783  "SELECT * FROM svy_answer WHERE active_fi = %s",
2784  array('integer'),
2785  array($active_id)
2786  );
2787  while ($row = $ilDB->fetchAssoc($result)) {
2788  if (!is_array($answers[$row["question_fi"]])) {
2789  $answers[$row["question_fi"]] = array();
2790  }
2791  $answers[$row["question_fi"]][] = $row;
2792  }
2793  $userdata = $this->getUserDataFromActiveId($active_id);
2794  $resultset = array(
2795  "name" => $userdata["fullname"],
2796  "firstname" => $userdata["firstname"],
2797  "lastname" => $userdata["lastname"],
2798  "login" => $userdata["login"],
2799  "gender" => $userdata["gender"],
2800  "answers" => array()
2801  );
2802  foreach ($questions as $key => $question) {
2803  if (array_key_exists($key, $answers)) {
2804  $resultset["answers"][$key] = $answers[$key];
2805  } else {
2806  $resultset["answers"][$key] = array();
2807  }
2808  sort($resultset["answers"][$key]);
2809  }
2810  return $resultset;
2811  }
2812 
2817  public function getQuestionsTable(
2818  array $arrFilter
2819  ): array {
2820  $ilDB = $this->db;
2821  $where = "";
2822  if (array_key_exists('title', $arrFilter) && strlen($arrFilter['title'])) {
2823  $where .= " AND " . $ilDB->like('svy_question.title', 'text', "%%" . $arrFilter['title'] . "%%");
2824  }
2825  if (array_key_exists('description', $arrFilter) && strlen($arrFilter['description'])) {
2826  $where .= " AND " . $ilDB->like('svy_question.description', 'text', "%%" . $arrFilter['description'] . "%%");
2827  }
2828  if (array_key_exists('author', $arrFilter) && strlen($arrFilter['author'])) {
2829  $where .= " AND " . $ilDB->like('svy_question.author', 'text', "%%" . $arrFilter['author'] . "%%");
2830  }
2831  if (array_key_exists('type', $arrFilter) && strlen($arrFilter['type'])) {
2832  $where .= " AND svy_qtype.type_tag = " . $ilDB->quote($arrFilter['type'], 'text');
2833  }
2834  if (array_key_exists('spl', $arrFilter) && strlen($arrFilter['spl'])) {
2835  $where .= " AND svy_question.obj_fi = " . $ilDB->quote($arrFilter['spl'], 'integer');
2836  }
2837 
2838  $spls = $this->getAvailableQuestionpools(true, false, false);
2839  $forbidden = " AND " . $ilDB->in('svy_question.obj_fi', array_keys($spls), false, 'integer');
2840  $forbidden .= " AND svy_question.complete = " . $ilDB->quote("1", 'text');
2841  $existing = "";
2842  $existing_questions = $this->getExistingQuestions();
2843  if (count($existing_questions)) {
2844  $existing = " AND " . $ilDB->in('svy_question.question_id', $existing_questions, true, 'integer');
2845  }
2846 
2848 
2849  $query_result = $ilDB->query("SELECT svy_question.*, svy_qtype.type_tag, svy_qtype.plugin, object_reference.ref_id" .
2850  " FROM svy_question, svy_qtype, object_reference" .
2851  " WHERE svy_question.original_id IS NULL" . $forbidden . $existing .
2852  " AND svy_question.obj_fi = object_reference.obj_id AND svy_question.tstamp > 0" .
2853  " AND svy_question.questiontype_fi = svy_qtype.questiontype_id " . $where);
2854 
2855  $rows = array();
2856  if ($query_result->numRows()) {
2857  while ($row = $ilDB->fetchAssoc($query_result)) {
2858  if (array_key_exists('spl_txt', $arrFilter) && strlen($arrFilter['spl_txt'])) {
2859  if (stripos($spls[$row["obj_fi"]], $arrFilter['spl_txt']) === false) {
2860  continue;
2861  }
2862  }
2863 
2864  $row['ttype'] = $trans[$row['type_tag']];
2865  if ($row["plugin"]) {
2866  continue;
2867  } else {
2868  $rows[] = $row;
2869  }
2870  }
2871  }
2872  return $rows;
2873  }
2874 
2879  public function getQuestionblocksTable(
2880  array $arrFilter
2881  ): array {
2882  $ilDB = $this->db;
2883 
2884  $where = "";
2885  if (array_key_exists('title', $arrFilter) && strlen($arrFilter['title'])) {
2886  $where .= " AND " . $ilDB->like('svy_qblk.title', 'text', "%%" . $arrFilter['title'] . "%%");
2887  }
2888 
2889  $query_result = $ilDB->query("SELECT svy_qblk.*, svy_svy.obj_fi FROM svy_qblk , svy_qblk_qst, svy_svy WHERE " .
2890  "svy_qblk.questionblock_id = svy_qblk_qst.questionblock_fi AND svy_svy.survey_id = svy_qblk_qst.survey_fi " .
2891  "$where GROUP BY svy_qblk.questionblock_id, svy_qblk.title, svy_qblk.show_questiontext, svy_qblk.show_blocktitle, " .
2892  "svy_qblk.owner_fi, svy_qblk.tstamp, svy_svy.obj_fi");
2893  $rows = array();
2894  if ($query_result->numRows()) {
2895  $survey_ref_ids = ilUtil::_getObjectsByOperations("svy", "write");
2896  $surveytitles = array();
2897  foreach ($survey_ref_ids as $survey_ref_id) {
2898  $survey_id = ilObject::_lookupObjId($survey_ref_id);
2899  $surveytitles[$survey_id] = ilObject::_lookupTitle($survey_id);
2900  }
2901  while ($row = $ilDB->fetchAssoc($query_result)) {
2902  $questions_array = $this->getQuestionblockQuestions($row["questionblock_id"]);
2903  $counter = 1;
2904  foreach ($questions_array as $key => $value) {
2905  $questions_array[$key] = "$counter. $value";
2906  $counter++;
2907  }
2908  if (strlen($surveytitles[$row["obj_fi"]] ?? "")) { // only questionpools which are not in trash
2909  $rows[$row["questionblock_id"]] = array(
2910  "questionblock_id" => $row["questionblock_id"],
2911  "title" => $row["title"],
2912  "svy" => $surveytitles[$row["obj_fi"]],
2913  "contains" => implode(", ", $questions_array),
2914  "owner" => $row["owner_fi"]
2915  );
2916  }
2917  }
2918  }
2919  return $rows;
2920  }
2921 
2926  public function toXML(): string
2927  {
2928  $a_xml_writer = new ilXmlWriter();
2929  // set xml header
2930  $a_xml_writer->xmlHeader();
2931  $attrs = array(
2932  "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
2933  "xsi:noNamespaceSchemaLocation" => "https://www.ilias.de/download/xsd/ilias_survey_4_2.xsd"
2934  );
2935  $a_xml_writer->xmlStartTag("surveyobject", $attrs);
2936  $attrs = array(
2937  "id" => $this->getSurveyId(),
2938  "title" => $this->getTitle()
2939  );
2940  $a_xml_writer->xmlStartTag("survey", $attrs);
2941 
2942  $a_xml_writer->xmlElement("description", null, $this->getDescription());
2943  $a_xml_writer->xmlElement("author", null, $this->getAuthor());
2944  $a_xml_writer->xmlStartTag("objectives");
2945  $attrs = array(
2946  "label" => "introduction"
2947  );
2948  $this->addMaterialTag($a_xml_writer, $this->getIntroduction(), true, true, $attrs);
2949  $attrs = array(
2950  "label" => "outro"
2951  );
2952  $this->addMaterialTag($a_xml_writer, $this->getOutro(), true, true, $attrs);
2953  $a_xml_writer->xmlEndTag("objectives");
2954 
2955  if ($this->getAnonymize()) {
2956  $attribs = array("enabled" => "1");
2957  } else {
2958  $attribs = array("enabled" => "0");
2959  }
2960  $a_xml_writer->xmlElement("anonymisation", $attribs);
2961  $a_xml_writer->xmlStartTag("restrictions");
2962  if ($this->getAnonymize() === 2) {
2963  $attribs = array("type" => "free");
2964  } else {
2965  $attribs = array("type" => "restricted");
2966  }
2967  $a_xml_writer->xmlElement("access", $attribs);
2968  if ($this->getStartDate()) {
2969  $attrs = array("type" => "date");
2970  preg_match("/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/", $this->getStartDate(), $matches);
2971  $a_xml_writer->xmlElement("startingtime", $attrs, sprintf("%04d-%02d-%02dT%02d:%02d:00", $matches[1], $matches[2], $matches[3], $matches[4], $matches[5]));
2972  }
2973  if ($this->getEndDate()) {
2974  $attrs = array("type" => "date");
2975  preg_match("/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/", $this->getEndDate(), $matches);
2976  $a_xml_writer->xmlElement("endingtime", $attrs, sprintf("%04d-%02d-%02dT%02d:%02d:00", $matches[1], $matches[2], $matches[3], $matches[4], $matches[5]));
2977  }
2978  $a_xml_writer->xmlEndTag("restrictions");
2979 
2980  // constraints
2981  $pages = $this->getSurveyPages();
2982  $hasconstraints = false;
2983  foreach ($pages as $question_array) {
2984  foreach ($question_array as $question) {
2985  if (count($question["constraints"])) {
2986  $hasconstraints = true;
2987  }
2988  }
2989  }
2990 
2991  if ($hasconstraints) {
2992  $a_xml_writer->xmlStartTag("constraints");
2993  foreach ($pages as $question_array) {
2994  foreach ($question_array as $question) {
2995  if (count($question["constraints"])) {
2996  // found constraints
2997  foreach ($question["constraints"] as $constraint) {
2998  $attribs = array(
2999  "sourceref" => $question["question_id"],
3000  "destref" => $constraint["question"],
3001  "relation" => $constraint["short"],
3002  "value" => $constraint["value"],
3003  "conjunction" => $constraint["conjunction"]
3004  );
3005  $a_xml_writer->xmlElement("constraint", $attribs);
3006  }
3007  }
3008  }
3009  }
3010  $a_xml_writer->xmlEndTag("constraints");
3011  }
3012 
3013  // add the rest of the preferences in qtimetadata tags, because there is no correspondent definition in QTI
3014  $a_xml_writer->xmlStartTag("metadata");
3015 
3016  $custom_properties = array();
3017  $custom_properties["evaluation_access"] = $this->getEvaluationAccess();
3018  $custom_properties["status"] = !$this->getOfflineStatus();
3019  $custom_properties["display_question_titles"] = $this->getShowQuestionTitles();
3020 
3021  $custom_properties["own_results_view"] = (int) $this->hasViewOwnResults();
3022  $custom_properties["own_results_mail"] = (int) $this->hasMailOwnResults();
3023  $custom_properties["confirmation_mail"] = (int) $this->hasMailConfirmation();
3024 
3025  $custom_properties["anon_user_list"] = (int) $this->hasAnonymousUserList();
3026  $custom_properties["mode"] = $this->getMode();
3027  $custom_properties["mode_360_self_eval"] = (int) $this->get360SelfEvaluation();
3028  $custom_properties["mode_360_self_rate"] = (int) $this->get360SelfRaters();
3029  $custom_properties["mode_360_self_appr"] = (int) $this->get360SelfAppraisee();
3030  $custom_properties["mode_360_results"] = $this->get360Results();
3031  $custom_properties["mode_skill_service"] = (int) $this->getSkillService();
3032  $custom_properties["mode_self_eval_results"] = $this->getSelfEvaluationResults();
3033 
3034 
3035  // :TODO: skills?
3036 
3037  // reminder/tutor notification are (currently?) not exportable
3038 
3039  foreach ($custom_properties as $label => $value) {
3040  $a_xml_writer->xmlStartTag("metadatafield");
3041  $a_xml_writer->xmlElement("fieldlabel", null, $label);
3042  $a_xml_writer->xmlElement("fieldentry", null, $value);
3043  $a_xml_writer->xmlEndTag("metadatafield");
3044  }
3045 
3046  $a_xml_writer->xmlEndTag("metadata");
3047  $a_xml_writer->xmlEndTag("survey");
3048 
3049  $attribs = array("id" => $this->getId());
3050  $a_xml_writer->xmlStartTag("surveyquestions", $attribs);
3051  // add questionblock descriptions
3052  foreach ($pages as $question_array) {
3053  if (count($question_array) > 1) {
3054  $attribs = array("id" => $question_array[0]["question_id"]);
3055  $attribs = array(
3056  "showQuestiontext" => $question_array[0]["questionblock_show_questiontext"],
3057  "showBlocktitle" => $question_array[0]["questionblock_show_blocktitle"],
3058  "compressView" => $question_array[0]["questionblock_compress_view"]
3059  );
3060  $a_xml_writer->xmlStartTag("questionblock", $attribs);
3061  if (strlen($question_array[0]["questionblock_title"])) {
3062  $a_xml_writer->xmlElement("questionblocktitle", null, $question_array[0]["questionblock_title"]);
3063  }
3064  }
3065  foreach ($question_array as $question) {
3066  if (strlen($question["heading"] ?? "")) {
3067  $a_xml_writer->xmlElement("textblock", null, $question["heading"]);
3068  }
3069  $questionObject = self::_instanciateQuestion($question["question_id"]);
3070  //questionObject contains all the fields from the database. (loadFromDb)
3071  //we don't need the value from svy_qst_oblig table, we already have the values from svy_question table.
3072  //if ($questionObject !== FALSE) $questionObject->insertXML($a_xml_writer, FALSE, $obligatory_states[$question["question_id"]]);
3073  if ($questionObject !== false) {
3074  $questionObject->insertXML($a_xml_writer, false);
3075  }
3076  }
3077  if (count($question_array) > 1) {
3078  $a_xml_writer->xmlEndTag("questionblock");
3079  }
3080  }
3081 
3082  $a_xml_writer->xmlEndTag("surveyquestions");
3083  $a_xml_writer->xmlEndTag("surveyobject");
3084  $xml = $a_xml_writer->xmlDumpMem(false);
3085  return $xml;
3086  }
3087 
3092  public static function _instanciateQuestion(
3093  int $question_id
3094  ): ?SurveyQuestion {
3095  if ($question_id < 1) {
3096  return null;
3097  }
3098  $question_type = SurveyQuestion::_getQuestionType($question_id);
3099  if ($question_type === '') {
3100  return null;
3101  }
3102  SurveyQuestion::_includeClass($question_type);
3103  $question = new $question_type();
3104  $question->loadFromDb($question_id);
3105  return $question;
3106  }
3107 
3113  public function locateImportFiles(
3114  string $a_dir
3115  ): ?array {
3116  if (!is_dir($a_dir) || is_int(strpos($a_dir, ".."))) {
3117  return null;
3118  }
3119  $importDirectory = "";
3120  $xmlFile = "";
3121 
3122  $current_dir = opendir($a_dir);
3123  $files = array();
3124  while ($entryname = readdir($current_dir)) {
3125  $files[] = $entryname;
3126  }
3127 
3128  foreach ($files as $file) {
3129  if (is_dir($a_dir . "/" . $file) and ($file !== "." and $file !== "..")) {
3130  // found directory created by zip
3131  $importDirectory = $a_dir . "/" . $file;
3132  }
3133  }
3134  closedir($current_dir);
3135  if ($importDirectory !== '') {
3136  // find the xml file
3137  $current_dir = opendir($importDirectory);
3138  $files = array();
3139  while ($entryname = readdir($current_dir)) {
3140  $files[] = $entryname;
3141  }
3142  foreach ($files as $file) {
3143  if (is_file($importDirectory . "/" . $file) &&
3144  ($file !== "." && $file !== "..") &&
3145  (preg_match("/^[0-9]{10}__[0-9]+__(svy_)*[0-9]+\.[A-Za-z]{1,3}$/", $file) ||
3146  preg_match("/^[0-9]{10}__[0-9]+__(survey__)*[0-9]+\.[A-Za-z]{1,3}$/", $file))) {
3147  // found xml file
3148  $xmlFile = $importDirectory . "/" . $file;
3149  }
3150  }
3151  }
3152  return array("dir" => $importDirectory, "xml" => $xmlFile);
3153  }
3154 
3162  public function importObject(
3163  array $file_info,
3164  int $svy_qpl_id
3165  ): string {
3166  if ($svy_qpl_id < 1) {
3167  $svy_qpl_id = -1;
3168  }
3169  // check if file was uploaded
3170  $source = $file_info["tmp_name"];
3171  $error = "";
3172  if (($source === 'none') || (!$source) || $file_info["error"] > UPLOAD_ERR_OK) {
3173  $error = $this->lng->txt("import_no_file_selected");
3174  }
3175  // check correct file type
3176  $isXml = false;
3177  $isZip = false;
3178  if ((strcmp($file_info["type"], "text/xml") === 0) || (strcmp($file_info["type"], "application/xml") === 0)) {
3179  $this->svy_log->debug("isXML");
3180  $isXml = true;
3181  }
3182  // too many different mime-types, so we use the suffix
3183  $suffix = pathinfo($file_info["name"]);
3184  if (strcmp(strtolower($suffix["extension"]), "zip") === 0) {
3185  $this->svy_log->debug("isZip");
3186  $isZip = true;
3187  }
3188  if (!$isXml && !$isZip) {
3189  $error = $this->lng->txt("import_wrong_file_type");
3190  $this->svy_log->debug("Survey: Import error. Filetype was \"" . $file_info["type"] . "\"");
3191  }
3192  if ($error === '') {
3193  // import file as a survey
3194  $import_dir = $this->getImportDirectory();
3195  $import_subdir = "";
3196  $importfile = "";
3197  if ($isZip) {
3198  $importfile = $import_dir . "/" . $file_info["name"];
3199  ilFileUtils::moveUploadedFile($source, $file_info["name"], $importfile);
3200  $this->domain->resources()->zip()->unzipFile($importfile);
3201  $found = $this->locateImportFiles($import_dir);
3202  if (!((strlen($found["dir"]) > 0) && (strlen($found["xml"]) > 0))) {
3203  $error = $this->lng->txt("wrong_import_file_structure");
3204  return $error;
3205  }
3206  $importfile = $found["xml"];
3207  $import_subdir = $found["dir"];
3208  } else {
3209  $importfile = tempnam($import_dir, "survey_import");
3210  ilFileUtils::moveUploadedFile($source, $file_info["name"], $importfile);
3211  }
3212 
3213  $this->svy_log->debug("Import file = $importfile");
3214  $this->svy_log->debug("Import subdir = $import_subdir");
3215 
3216  $fh = fopen($importfile, 'rb');
3217  if (!$fh) {
3218  $error = $this->lng->txt("import_error_opening_file");
3219  return $error;
3220  }
3221  $xml = fread($fh, filesize($importfile));
3222  $result = fclose($fh);
3223  if (!$result) {
3224  $error = $this->lng->txt("import_error_closing_file");
3225  return $error;
3226  }
3227 
3228  $this->import_manager->clearMobs();
3229  if (strpos($xml, "questestinterop")) {
3230  throw new ilInvalidSurveyImportFileException("Unsupported survey version (< 3.8) found.");
3231  } else {
3232  $this->svy_log->debug("survey id = " . $this->getId());
3233  $this->svy_log->debug("question pool id = " . $svy_qpl_id);
3234 
3235  $imp = new ilImport();
3236  $config = $imp->getConfig("components/ILIAS/Survey");
3237  $config->setQuestionPoolID($svy_qpl_id);
3238  $imp->getMapping()->addMapping("components/ILIAS/Survey", "svy", 0, $this->getId());
3239  $imp->importFromDirectory($import_subdir, "svy", "components/ILIAS/Survey");
3240  $this->svy_log->debug("config(Modules/survey)->getQuestionPoolId =" . $config->getQuestionPoolID());
3241  }
3242  }
3243  return $error;
3244  }
3245 
3246  public function cloneObject(int $target_id, int $copy_id = 0, bool $omit_tree = false): ?ilObject
3247  {
3248  $ilDB = $this->db;
3249 
3250  $this->loadFromDb();
3251 
3252  //survey mode
3253  $svy_type = $this->getMode();
3254 
3256  $newObj = parent::cloneObject($target_id, $copy_id, $omit_tree);
3257  $this->cloneMetaData($newObj);
3258  $newObj->updateMetaData();
3259 
3260  $newObj->setAuthor($this->getAuthor());
3261  $newObj->setIntroduction($this->getIntroduction());
3262  $newObj->setOutro($this->getOutro());
3263  $newObj->setEvaluationAccess($this->getEvaluationAccess());
3264  $newObj->setStartDate($this->getStartDate());
3265  $newObj->setEndDate($this->getEndDate());
3266  $newObj->setAnonymize($this->getAnonymize());
3267  $newObj->setShowQuestionTitles($this->getShowQuestionTitles());
3268  $newObj->setMailOwnResults($this->hasMailOwnResults());
3269  $newObj->setMailConfirmation($this->hasMailConfirmation());
3270  $newObj->setAnonymousUserList($this->hasAnonymousUserList());
3271 
3272  $newObj->setMode($this->getMode());
3273  $newObj->set360SelfEvaluation($this->get360SelfEvaluation());
3274  $newObj->set360SelfAppraisee($this->get360SelfAppraisee());
3275  $newObj->set360SelfRaters($this->get360SelfRaters());
3276  $newObj->set360Results($this->get360Results());
3277  $newObj->setSkillService($this->getSkillService());
3278 
3279  // reminder/notification
3280  $newObj->setReminderStatus($this->getReminderStatus());
3281  $newObj->setReminderStart($this->getReminderStart());
3282  $newObj->setReminderEnd($this->getReminderEnd());
3283  $newObj->setReminderFrequency($this->getReminderFrequency());
3284  $newObj->setReminderTarget($this->getReminderTarget());
3285  $newObj->setReminderTemplate($this->getReminderTemplate());
3286  // reminder_last_sent must not be copied!
3287  $newObj->setTutorNotificationStatus($this->getTutorNotificationStatus());
3288  $newObj->setTutorNotificationRecipients($this->getTutorNotificationRecipients());
3289  $newObj->setTutorNotificationTarget($this->getTutorNotificationTarget());
3290  $newObj->setTutorResultsStatus($this->getTutorResultsStatus());
3291  $newObj->setTutorResultsRecipients($this->getTutorResultsRecipients());
3292 
3293  $newObj->setMailNotification($this->getMailNotification());
3294  $newObj->setMailAddresses($this->getMailAddresses());
3295  $newObj->setMailParticipantData($this->getMailParticipantData());
3296 
3297  $question_pointer = array();
3298  // clone the questions
3299  $mapping = array();
3300 
3301  foreach ($this->questions as $key => $question_id) {
3303  $question = self::_instanciateQuestion($question_id);
3304  if ($question) { // #10824
3305  $question->id = -1;
3306  $original_id = SurveyQuestion::_getOriginalId($question_id, false);
3307  $question->setObjId($newObj->getId());
3308  $question->saveToDb($original_id);
3309  $newObj->questions[$key] = $question->getId();
3310  $question_pointer[$question_id] = $question->getId();
3311  $mapping[$question_id] = $question->getId();
3312  }
3313  }
3314 
3315  //copy online status if object is not the root copy object
3316  $cp_options = ilCopyWizardOptions::_getInstance($copy_id);
3317 
3318  if (!$cp_options->isRootNode($this->getRefId())) {
3319  $newObj->setOfflineStatus($this->getOfflineStatus());
3320  }
3321 
3322  $newObj->saveToDb();
3323  $newObj->cloneTextblocks($mapping);
3324 
3325  // #14929
3326  if (($svy_type === self::MODE_360 || $svy_type === self::MODE_SELF_EVAL) &&
3327  $this->getSkillService()) {
3328  $src_skills = new ilSurveySkill($this);
3329  $tgt_skills = new ilSurveySkill($newObj);
3330 
3331  foreach ($mapping as $src_qst_id => $tgt_qst_id) {
3332  $qst_skill = $src_skills->getSkillForQuestion($src_qst_id);
3333  if ($qst_skill) {
3334  $tgt_skills->addQuestionSkillAssignment($tgt_qst_id, $qst_skill["base_skill_id"], $qst_skill["tref_id"]);
3335  }
3336  }
3337 
3338  $thresholds = new ilSurveySkillThresholds($this);
3339  $thresholds->cloneTo($newObj, $mapping);
3340  }
3341 
3342  // clone the questionblocks
3343  $questionblocks = array();
3344  $questionblock_questions = array();
3345  $result = $ilDB->queryF(
3346  "SELECT * FROM svy_qblk_qst WHERE survey_fi = %s",
3347  array('integer'),
3348  array($this->getSurveyId())
3349  );
3350  if ($result->numRows() > 0) {
3351  while ($row = $ilDB->fetchAssoc($result)) {
3352  $questionblock_questions[] = $row;
3353  $questionblocks[$row["questionblock_fi"]] = $row["questionblock_fi"];
3354  }
3355  }
3356  // create new questionblocks
3357  foreach ($questionblocks as $key => $value) {
3358  $questionblock = self::_getQuestionblock($key);
3359  $questionblock_id = self::_addQuestionblock(
3360  (string) $questionblock["title"],
3361  (int) $questionblock["owner_fi"],
3362  (bool) $questionblock["show_questiontext"],
3363  (bool) $questionblock["show_blocktitle"],
3364  (bool) $questionblock["compress_view"]
3365  );
3366  $questionblocks[$key] = $questionblock_id;
3367  }
3368  // create new questionblock questions
3369  foreach ($questionblock_questions as $key => $value) {
3370  if ($questionblocks[$value["questionblock_fi"]] &&
3371  $question_pointer[$value["question_fi"]]) {
3372  $next_id = $ilDB->nextId('svy_qblk_qst');
3373  $affectedRows = $ilDB->manipulateF(
3374  "INSERT INTO svy_qblk_qst (qblk_qst_id, survey_fi, questionblock_fi, question_fi) " .
3375  "VALUES (%s, %s, %s, %s)",
3376  array('integer','integer','integer','integer'),
3377  array($next_id, $newObj->getSurveyId(), $questionblocks[$value["questionblock_fi"]], $question_pointer[$value["question_fi"]])
3378  );
3379  }
3380  }
3381 
3382  // clone the constraints
3383  $constraints = self::_getConstraints($this->getSurveyId());
3384  $newConstraints = array();
3385  foreach ($constraints as $key => $constraint) {
3386  if ($question_pointer[$constraint["for_question"]] &&
3387  $question_pointer[$constraint["question"]]) {
3388  if (!array_key_exists($constraint['id'], $newConstraints)) {
3389  $constraint_id = $newObj->addConstraint($question_pointer[$constraint["question"]], $constraint["relation_id"], $constraint["value"], $constraint['conjunction']);
3390  $newConstraints[$constraint['id']] = $constraint_id;
3391  }
3392  $newObj->addConstraintToQuestion($question_pointer[$constraint["for_question"]], $newConstraints[$constraint['id']]);
3393  }
3394  }
3395 
3396  // #16210 - clone LP settings
3397  $obj_settings = new ilLPObjSettings($this->getId());
3398  $obj_settings->cloneSettings($newObj->getId());
3399  unset($obj_settings);
3400 
3401  return $newObj;
3402  }
3403 
3407  public function getTextblock(
3408  int $question_id
3409  ): string {
3410  $ilDB = $this->db;
3411  $result = $ilDB->queryF(
3412  "SELECT * FROM svy_svy_qst WHERE question_fi = %s",
3413  array('integer'),
3414  array($question_id)
3415  );
3416  if ($row = $ilDB->fetchAssoc($result)) {
3417  return $row["heading"] ?? "";
3418  } else {
3419  return "";
3420  }
3421  }
3422 
3428  public function cloneTextblocks(
3429  array $mapping
3430  ): void {
3431  foreach ($mapping as $original_id => $new_id) {
3432  $textblock = $this->getTextblock($original_id);
3433  $this->saveHeading(ilUtil::stripSlashes($textblock, true, ilObjAdvancedEditing::_getUsedHTMLTagsAsString("survey")), $new_id);
3434  }
3435  }
3436 
3443  public function createExportDirectory(): void
3444  {
3445  $svy_data_dir = ilFileUtils::getDataDir() . "/svy_data";
3446  ilFileUtils::makeDir($svy_data_dir);
3447  if (!is_writable($svy_data_dir)) {
3448  throw new ilSurveyException("Survey Data Directory (" . $svy_data_dir . ") not writeable.");
3449  }
3450 
3451  // create learning module directory (data_dir/lm_data/lm_<id>)
3452  $svy_dir = $svy_data_dir . "/svy_" . $this->getId();
3453  ilFileUtils::makeDir($svy_dir);
3454  if (!is_dir($svy_dir)) {
3455  throw new ilSurveyException("Creation of Survey Directory failed.");
3456  }
3457  // create Export subdirectory (data_dir/lm_data/lm_<id>/Export)
3458  $export_dir = $svy_dir . "/export";
3459  ilFileUtils::makeDir($export_dir);
3460  if (!is_dir($export_dir)) {
3461  throw new ilSurveyException("Creation of Export Directory failed.");
3462  }
3463  }
3464 
3468  public function getExportDirectory(): string
3469  {
3470  $export_dir = ilFileUtils::getDataDir() . "/svy_data" . "/svy_" . $this->getId() . "/export";
3471 
3472  return $export_dir;
3473  }
3474 
3481  public function createImportDirectory(): void
3482  {
3483  $svy_data_dir = ilFileUtils::getDataDir() . "/svy_data";
3484  ilFileUtils::makeDir($svy_data_dir);
3485 
3486  if (!is_writable($svy_data_dir)) {
3487  throw new ilSurveyException("Survey Data Directory (" . $svy_data_dir . ") not writeable.");
3488  }
3489 
3490  // create test directory (data_dir/svy_data/svy_<id>)
3491  $svy_dir = $svy_data_dir . "/svy_" . $this->getId();
3492  ilFileUtils::makeDir($svy_dir);
3493  if (!is_dir($svy_dir)) {
3494  throw new ilSurveyException("Creation of Survey Directory failed.");
3495  }
3496 
3497  // create import subdirectory (data_dir/svy_data/svy_<id>/import)
3498  $import_dir = $svy_dir . "/import";
3499  ilFileUtils::makeDir($import_dir);
3500  if (!is_dir($import_dir)) {
3501  throw new ilSurveyException("Creation of Import Directory failed.");
3502  }
3503  }
3504 
3509  public function getImportDirectory(): string
3510  {
3511  $import_dir = ilFileUtils::getDataDir() . "/svy_data" .
3512  "/svy_" . $this->getId() . "/import";
3513  if (!is_dir($import_dir)) {
3514  ilFileUtils::makeDirParents($import_dir);
3515  }
3516  if (is_dir($import_dir)) {
3517  return $import_dir;
3518  } else {
3519  return "";
3520  }
3521  }
3522 
3528  public function saveHeading(
3529  string $heading,
3530  int $insertbefore
3531  ): void {
3532  $ilDB = $this->db;
3533  if ($heading) {
3534  $ilDB->manipulateF(
3535  "UPDATE svy_svy_qst SET heading=%s WHERE survey_fi=%s AND question_fi=%s",
3536  array('text','integer','integer'),
3537  array($heading, $this->getSurveyId(), $insertbefore)
3538  );
3539  } else {
3540  $ilDB->manipulateF(
3541  "UPDATE svy_svy_qst SET heading=%s WHERE survey_fi=%s AND question_fi=%s",
3542  array('text','integer','integer'),
3543  array(null, $this->getSurveyId(), $insertbefore)
3544  );
3545  }
3546  }
3547 
3552  public function isAnonymizedParticipant(string $key): bool
3553  {
3554  $ilDB = $this->db;
3555 
3556  $result = $ilDB->queryF(
3557  "SELECT finished_id FROM svy_finished WHERE anonymous_id = %s AND survey_fi = %s",
3558  array('text','integer'),
3559  array($key, $this->getSurveyId())
3560  );
3561  return $result->numRows() === 1;
3562  }
3563 
3573  public function getSurveyCodesForExport(
3574  ?array $a_codes = null,
3575  ?array $a_ids = null
3576  ): string {
3577  $ilDB = $this->db;
3578  $ilUser = $this->user;
3579  $lng = $this->lng;
3580 
3581  $sql = "SELECT svy_anonymous.*, svy_finished.state" .
3582  " FROM svy_anonymous" .
3583  " LEFT JOIN svy_finished ON (svy_anonymous.survey_key = svy_finished.anonymous_id)" .
3584  " WHERE svy_anonymous.survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
3585  " AND svy_anonymous.user_key IS NULL";
3586 
3587  if ($a_codes) {
3588  $sql .= " AND " . $ilDB->in("svy_anonymous.survey_key", $a_codes, "", "text");
3589  } elseif ($a_ids) {
3590  $sql .= " AND " . $ilDB->in("svy_anonymous.anonymous_id", $a_ids, "", "text");
3591  }
3592 
3593  $export = array();
3594 
3595  // #14905
3596  $titles = array();
3597  $titles[] = '"' . $lng->txt("survey_code") . '"';
3598  $titles[] = '"' . $lng->txt("email") . '"';
3599  $titles[] = '"' . $lng->txt("lastname") . '"';
3600  $titles[] = '"' . $lng->txt("firstname") . '"';
3601  $titles[] = '"' . $lng->txt("create_date") . '"';
3602  $titles[] = '"' . $lng->txt("used") . '"';
3603  $titles[] = '"' . $lng->txt("mail_sent_short") . '"';
3604  $titles[] = '"' . $lng->txt("survey_code_url") . '"';
3605  $export[] = implode(";", $titles);
3606 
3607  $result = $ilDB->query($sql);
3608  $default_lang = $ilUser->getPref("survey_code_language");
3609  while ($row = $ilDB->fetchAssoc($result)) {
3610  $item = array();
3611  $item[] = $row["survey_key"];
3612 
3613  if ($row["externaldata"]) {
3614  $ext = unserialize((string) $row["externaldata"], ['allowed_classes' => false]);
3615  $item[] = $ext["email"];
3616  $item[] = $ext["lastname"];
3617  $item[] = $ext["firstname"];
3618  } else {
3619  $item[] = "";
3620  $item[] = "";
3621  $item[] = "";
3622  }
3623 
3624  // No relative (today, tomorrow...) dates in export.
3625  $date = new ilDateTime($row['tstamp'], IL_CAL_UNIX);
3626  $item[] = $date->get(IL_CAL_DATETIME);
3627 
3628  $item[] = ($this->isSurveyCodeUsed($row["survey_key"])) ? 1 : 0;
3629  $item[] = ($row["sent"]) ? 1 : 0;
3630 
3631  $params = array("accesscode" => $row["survey_key"]);
3632  if ($default_lang) {
3633  $params["lang"] = $default_lang;
3634  }
3635  $item[] = ilLink::_getLink($this->getRefId(), "svy", $params);
3636 
3637  $export[] = '"' . implode('";"', $item) . '"';
3638  }
3639  return implode("\n", $export);
3640  }
3641 
3647  public function getSurveyCodesTableData(
3648  ?array $ids = null,
3649  ?string $lang = null
3650  ): array {
3651  $ilDB = $this->db;
3652 
3653  $codes = array();
3654 
3655  $sql = "SELECT svy_anonymous.*, svy_finished.state" .
3656  " FROM svy_anonymous" .
3657  " LEFT JOIN svy_finished ON (svy_anonymous.survey_key = svy_finished.anonymous_id)" .
3658  " WHERE svy_anonymous.survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") /*.
3659  " AND svy_anonymous.user_key IS NULL" */; // #15860
3660 
3661  if ($ids) {
3662  $sql .= " AND " . $ilDB->in("svy_anonymous.anonymous_id", $ids, "", "integer");
3663  }
3664 
3665  $sql .= " ORDER BY tstamp, survey_key ASC";
3666  $result = $ilDB->query($sql);
3667  if ($result->numRows() > 0) {
3668  while ($row = $ilDB->fetchAssoc($result)) {
3669  $href = "";
3670  $used = false;
3671  if ($this->isSurveyCodeUsed($row["survey_key"])) {
3672  $used = true;
3673  } else {
3674  $params = array("accesscode" => $row["survey_key"]);
3675  if ($lang) {
3676  $params["lang"] = $lang;
3677  }
3678  $href = ilLink::_getLink($this->getRefId(), "svy", $params);
3679  }
3680 
3681 
3682  $item = array(
3683  'id' => $row["anonymous_id"],
3684  'code' => $row["survey_key"],
3685  'date' => $row["tstamp"],
3686  'used' => $used,
3687  'sent' => $row['sent'],
3688  'href' => $href,
3689  'email' => '',
3690  'last_name' => '',
3691  'first_name' => ''
3692  );
3693 
3694  if ($row["externaldata"]) {
3695  $ext = unserialize((string) $row["externaldata"], ['allowed_classes' => false]);
3696  $item['email'] = $ext['email'] ?? "";
3697  $item['last_name'] = $ext['lastname'] ?? "";
3698  $item['first_name'] = $ext['firstname'] ?? "";
3699  }
3700 
3701  $codes[] = $item;
3702  }
3703  }
3704  return $codes;
3705  }
3706 
3710  public function isSurveyCodeUsed(
3711  string $code
3712  ): bool {
3713  $ilDB = $this->db;
3714  $result = $ilDB->queryF(
3715  "SELECT finished_id FROM svy_finished WHERE survey_fi = %s AND anonymous_id = %s",
3716  array('integer','text'),
3717  array($this->getSurveyId(), $code)
3718  );
3719  return $result->numRows() > 0;
3720  }
3721 
3725  public function isSurveyCodeUnique(
3726  string $code
3727  ): bool {
3728  $ilDB = $this->db;
3729  $result = $ilDB->queryF(
3730  "SELECT anonymous_id FROM svy_anonymous WHERE survey_fi = %s AND survey_key = %s",
3731  array('integer','text'),
3732  array($this->getSurveyId(), $code)
3733  );
3734  return !(($result->numRows() > 0));
3735  }
3736 
3740  public function importSurveyCode(
3741  string $a_anonymize_key,
3742  int $a_created,
3743  array $a_data
3744  ): void {
3745  $ilDB = $this->db;
3746 
3747  $next_id = $ilDB->nextId('svy_anonymous');
3748  $ilDB->manipulateF(
3749  "INSERT INTO svy_anonymous (anonymous_id, survey_key, survey_fi, externaldata, tstamp) " .
3750  "VALUES (%s, %s, %s, %s, %s)",
3751  array('integer','text','integer','text','integer'),
3752  array($next_id, $a_anonymize_key, $this->getSurveyId(), serialize($a_data), $a_created)
3753  );
3754  }
3755 
3759  public function sendCodes(
3760  int $not_sent,
3761  string $subject,
3762  string $message,
3763  string $lang
3764  ): void {
3765  /*
3766  * 0 = all
3767  * 1 = not sent
3768  * 2 = finished
3769  * 3 = not finished
3770  */
3771  $check_finished = ($not_sent > 1);
3772 
3773 
3774  $mail = new ilMail(ANONYMOUS_USER_ID);
3775  $recipients = $this->getExternalCodeRecipients($check_finished);
3776  foreach ($recipients as $data) {
3777  if ($data['email'] && $data['code']) {
3778  $do_send = false;
3779  switch ($not_sent) {
3780  case 1:
3781  $do_send = !(bool) $data['sent'];
3782  break;
3783 
3784  case 2:
3785  $do_send = $data['finished'];
3786  break;
3787 
3788  case 3:
3789  $do_send = !$data['finished'];
3790  break;
3791 
3792  default:
3793  $do_send = true;
3794  break;
3795  }
3796  if ($do_send) {
3797  // build text
3798  $messagetext = $message;
3799  $url = ilLink::_getLink(
3800  $this->getRefId(),
3801  "svy",
3802  array(
3803  "accesscode" => $data["code"],
3804  "lang" => $lang
3805  )
3806  );
3807  $messagetext = str_replace('[url]', $url, $messagetext);
3808  foreach ($data as $key => $value) {
3809  $messagetext = str_replace('[' . $key . ']', $value, $messagetext);
3810  }
3811 
3812  // send mail
3813  $mail->enqueue(
3814  $data['email'], // to
3815  "", // cc
3816  "", // bcc
3817  $subject, // subject
3818  $messagetext, // message
3819  array() // attachments
3820  );
3821  }
3822  }
3823  }
3824 
3825  $ilDB = $this->db;
3826  $ilDB->manipulateF(
3827  "UPDATE svy_anonymous SET sent = %s WHERE survey_fi = %s AND externaldata IS NOT NULL",
3828  array('integer','integer'),
3829  array(1, $this->getSurveyId())
3830  );
3831  }
3832 
3836  public function getExternalCodeRecipients(
3837  bool $a_check_finished = false
3838  ): array {
3839  $ilDB = $this->db;
3840  $result = $ilDB->queryF(
3841  "SELECT survey_key code, externaldata, sent FROM svy_anonymous WHERE survey_fi = %s",
3842  array('integer'),
3843  array($this->getSurveyId())
3844  );
3845  $res = array();
3846  while ($row = $ilDB->fetchAssoc($result)) {
3847  if (!$row['externaldata']) {
3848  continue;
3849  }
3850 
3851  $externaldata = unserialize((string) $row['externaldata'], ['allowed_classes' => false]);
3852  if (!$externaldata['email']) {
3853  continue;
3854  }
3855 
3856  $externaldata['code'] = $row['code'];
3857  $externaldata['sent'] = $row['sent'];
3858 
3859  if ($a_check_finished) {
3860  #23294
3861  //$externaldata['finished'] = $this->isSurveyCodeUsed($row['code']);
3862  $externaldata['finished'] = $this->isSurveyFinishedByCode($row['code']);
3863  }
3864 
3865  $res[] = $externaldata;
3866  }
3867  return $res;
3868  }
3869 
3876  public function isSurveyFinishedByCode(string $a_code): bool
3877  {
3878  $result = $this->db->queryF(
3879  "SELECT state FROM svy_finished WHERE survey_fi = %s AND anonymous_id = %s",
3880  array('integer','text'),
3881  array($this->getSurveyId(), $a_code)
3882  );
3883 
3884  $row = $this->db->fetchAssoc($result);
3885 
3886  return $row['state'] ?? false;
3887  }
3888 
3892  public function deleteSurveyCode(
3893  string $survey_code
3894  ): void {
3895  $ilDB = $this->db;
3896 
3897  if ($survey_code !== '') {
3898  $affectedRows = $ilDB->manipulateF(
3899  "DELETE FROM svy_anonymous WHERE survey_fi = %s AND survey_key = %s",
3900  array('integer', 'text'),
3901  array($this->getSurveyId(), $survey_code)
3902  );
3903  }
3904  }
3905 
3912  public function getUserAccessCode(int $user_id): string
3913  {
3914  $ilDB = $this->db;
3915  $access_code = "";
3916  $result = $ilDB->queryF(
3917  "SELECT survey_key FROM svy_anonymous WHERE survey_fi = %s AND user_key = %s",
3918  array('integer','text'),
3919  array($this->getSurveyId(), md5($user_id))
3920  );
3921  if ($result->numRows()) {
3922  $row = $ilDB->fetchAssoc($result);
3923  $access_code = $row["survey_key"];
3924  }
3925  return $access_code;
3926  }
3927 
3934  public function saveUserAccessCode(
3935  int $user_id,
3936  string $access_code
3937  ): void {
3938  $ilDB = $this->db;
3939 
3940  // not really sure what to do about ANONYMOUS_USER_ID
3941 
3942  $next_id = $ilDB->nextId('svy_anonymous');
3943  $affectedRows = $ilDB->manipulateF(
3944  "INSERT INTO svy_anonymous (anonymous_id, survey_key, survey_fi, user_key, tstamp) " .
3945  "VALUES (%s, %s, %s, %s, %s)",
3946  array('integer','text', 'integer', 'text', 'integer'),
3947  array($next_id, $access_code, $this->getSurveyId(), md5($user_id), time())
3948  );
3949  }
3950 
3951 
3955  public function getLastAccess(
3956  int $finished_id
3957  ): ?int {
3958  $ilDB = $this->db;
3959 
3960  $result = $ilDB->queryF(
3961  "SELECT tstamp FROM svy_answer WHERE active_fi = %s ORDER BY tstamp DESC",
3962  array('integer'),
3963  array($finished_id)
3964  );
3965  if ($result->numRows()) {
3966  $row = $ilDB->fetchAssoc($result);
3967  return (int) $row["tstamp"];
3968  } else {
3969  $result = $ilDB->queryF(
3970  "SELECT tstamp FROM svy_finished WHERE finished_id = %s",
3971  array('integer'),
3972  array($finished_id)
3973  );
3974  if ($result->numRows()) {
3975  $row = $ilDB->fetchAssoc($result);
3976  return (int) $row["tstamp"];
3977  }
3978  }
3979  return null;
3980  }
3981 
3985  public function prepareTextareaOutput(
3986  string $txt_output
3987  ): string {
3989  }
3990 
3995  public function isHTML(
3996  string $a_text
3997  ): bool {
3998  if (preg_match("/<[^>]*?>/", $a_text)) {
3999  return true;
4000  } else {
4001  return false;
4002  }
4003  }
4004 
4009  public function addMaterialTag(
4010  ilXmlWriter $a_xml_writer,
4011  string $a_material,
4012  bool $close_material_tag = true,
4013  bool $add_mobs = true,
4014  ?array $attribs = null
4015  ): void {
4016  $a_xml_writer->xmlStartTag("material", $attribs);
4017  $attrs = array(
4018  "type" => "text/plain"
4019  );
4020  if ($this->isHTML($a_material)) {
4021  $attrs["type"] = "text/xhtml";
4022  }
4023  $mattext = ilRTE::_replaceMediaObjectImageSrc($a_material, 0);
4024  $a_xml_writer->xmlElement("mattext", $attrs, $mattext);
4025 
4026  if ($add_mobs) {
4027  $mobs = ilObjMediaObject::_getMobsOfObject("svy:html", $this->getId());
4028  foreach ($mobs as $mob) {
4029  $mob_id = "il_" . IL_INST_ID . "_mob_" . $mob;
4030  if (strpos($mattext, $mob_id) !== false) {
4031  $mob_obj = new ilObjMediaObject($mob);
4032  $imgattrs = array(
4033  "label" => $mob_id,
4034  "uri" => "objects/" . "il_" . IL_INST_ID . "_mob_" . $mob . "/" . $mob_obj->getTitle(),
4035  "type" => "svy:html",
4036  "id" => $this->getId()
4037  );
4038  $a_xml_writer->xmlElement("matimage", $imgattrs, null);
4039  }
4040  }
4041  }
4042  if ($close_material_tag) {
4043  $a_xml_writer->xmlEndTag("material");
4044  }
4045  }
4046 
4054  public function canExportSurveyCode(): bool
4055  {
4056  if ($this->getAnonymize() !== self::ANONYMIZE_OFF) {
4057  if ($this->surveyCodeSecurity === false) {
4058  return true;
4059  }
4060  }
4061  return false;
4062  }
4063 
4064  public function setSurveyId(int $survey_id): void
4065  {
4066  $this->survey_id = $survey_id;
4067  }
4068 
4076  public function getUserData(
4077  array $ids
4078  ): array {
4079  $ilDB = $this->db;
4080 
4081  if (count($ids) === 0) {
4082  return array();
4083  }
4084 
4085  $result = $ilDB->query("SELECT usr_id, login, lastname, firstname FROM usr_data WHERE " . $ilDB->in('usr_id', $ids, false, 'integer') . " ORDER BY login");
4086  $result_array = array();
4087  while ($row = $ilDB->fetchAssoc($result)) {
4088  $result_array[$row["usr_id"]] = $row;
4089  }
4090  return $result_array;
4091  }
4092 
4096  public function getMailNotification(): bool
4097  {
4098  return $this->mailnotification;
4099  }
4100 
4104  public function setMailNotification(bool $a_notification): void
4105  {
4106  $this->mailnotification = $a_notification;
4107  }
4108 
4112  public function getMailAddresses(): string
4113  {
4114  return $this->mailaddresses;
4115  }
4116 
4120  public function setMailAddresses(string $a_addresses): void
4121  {
4122  $this->mailaddresses = $a_addresses;
4123  }
4124 
4128  public function getMailParticipantData(): string
4129  {
4131  }
4132 
4136  public function setMailParticipantData(string $a_data): void
4137  {
4138  $this->mailparticipantdata = $a_data;
4139  }
4140 
4145  int $finished_id
4146  ): int {
4147  $ilDB = $this->db;
4148 
4149  $result = $ilDB->queryF(
4150  "SELECT * FROM svy_times WHERE finished_fi = %s",
4151  array('integer'),
4152  array($finished_id)
4153  );
4154  $total = 0;
4155  while ($row = $ilDB->fetchAssoc($result)) {
4156  if ($row['left_page'] > 0 && $row['entered_page'] > 0) {
4157  $total += $row['left_page'] - $row['entered_page'];
4158  }
4159  }
4160  return $total;
4161  }
4162 
4163  public function updateOrder(
4164  array $a_order
4165  ): void {
4166  if (count($this->questions) === count($a_order)) {
4167  $this->questions = array_flip($a_order);
4168  $this->saveQuestionsToDb();
4169  }
4170  }
4171 
4172  public function getPoolUsage(): bool
4173  {
4174  return $this->pool_usage;
4175  }
4176 
4177  public function setPoolUsage(bool $a_value): void
4178  {
4179  $this->pool_usage = $a_value;
4180  }
4181 
4185  public function updateCode(
4186  int $a_id,
4187  string $a_email,
4188  string $a_last_name,
4189  string $a_first_name,
4190  int $a_sent
4191  ): bool {
4192  $ilDB = $this->db;
4193 
4194  $a_email = trim($a_email);
4195 
4196  // :TODO:
4197  if (($a_email && !ilUtil::is_email($a_email)) || $a_email === "") {
4198  return false;
4199  }
4200 
4201  $data = array("email" => $a_email,
4202  "lastname" => trim($a_last_name),
4203  "firstname" => trim($a_first_name));
4204 
4205  $fields = array(
4206  "externaldata" => array("text", serialize($data)),
4207  "sent" => array("integer", $a_sent)
4208  );
4209 
4210  $ilDB->update(
4211  "svy_anonymous",
4212  $fields,
4213  array("anonymous_id" => array("integer", $a_id))
4214  );
4215 
4216  return true;
4217  }
4218 
4219 
4220  //
4221  // 360°
4222  //
4223 
4224  public function get360Mode(): bool
4225  {
4226  if ($this->getMode() === self::MODE_360) {
4227  return true;
4228  }
4229  return false;
4230  }
4231 
4232  public function set360SelfEvaluation(bool $a_value): void
4233  {
4234  $this->mode_360_self_eval = $a_value;
4235  }
4236 
4237  public function get360SelfEvaluation(): bool
4238  {
4240  }
4241 
4242  public function set360SelfAppraisee(bool $a_value): void
4243  {
4244  $this->mode_360_self_appr = $a_value;
4245  }
4246 
4247  public function get360SelfAppraisee(): bool
4248  {
4250  }
4251 
4252  public function set360SelfRaters(bool $a_value): void
4253  {
4254  $this->mode_360_self_rate = $a_value;
4255  }
4256 
4257  public function get360SelfRaters(): bool
4258  {
4260  }
4261 
4262  public function set360Results(int $a_value): void
4263  {
4264  $this->mode_360_results = $a_value;
4265  }
4266 
4267  public function get360Results(): int
4268  {
4269  return $this->mode_360_results;
4270  }
4271 
4275  public function addAppraisee(
4276  int $a_user_id
4277  ): void {
4278  global $DIC;
4279 
4280  $ilDB = $DIC->database();
4281  $access = $DIC->access();
4282 
4283  if (!$this->isAppraisee($a_user_id) &&
4284  $a_user_id !== ANONYMOUS_USER_ID) {
4285  $fields = array(
4286  "obj_id" => array("integer", $this->getSurveyId()),
4287  "user_id" => array("integer", $a_user_id)
4288  );
4289  $ilDB->insert("svy_360_appr", $fields);
4290 
4291  // send notification and add to desktop
4292  if ($access->checkAccessOfUser($a_user_id, "read", "", $this->getRefId())) {
4293  $this->sendAppraiseeNotification($a_user_id);
4294  }
4295  }
4296  }
4297 
4301  public function sendAppraiseeNotification(
4302  int $a_user_id
4303  ): void {
4304  $ntf = new ilSystemNotification();
4305  $ntf->setLangModules(array("svy", "survey"));
4306  $ntf->setRefId($this->getRefId());
4307  $ntf->setGotoLangId('url');
4308 
4309  // user specific language
4310  $lng = $ntf->getUserLanguage($a_user_id);
4311 
4312  $ntf->setIntroductionLangId("svy_user_added_appraisee_mail");
4313  $subject = str_replace("%1", $this->getTitle(), $lng->txt("svy_user_added_appraisee"));
4314 
4315  // #10044
4316  $mail = new ilMail(ANONYMOUS_USER_ID);
4317  $mail->enqueue(
4318  ilObjUser::_lookupLogin($a_user_id),
4319  "",
4320  "",
4321  $subject,
4322  $ntf->composeAndGetMessage($a_user_id, null, "read", true),
4323  []
4324  );
4325  }
4326 
4331  int $a_user_id
4332  ): void {
4333  $ntf = new ilSystemNotification();
4334  $ntf->setLangModules(array("svy", "survey"));
4335  $ntf->setRefId($this->getRefId());
4336  $ntf->setGotoLangId('url');
4337 
4338  // user specific language
4339  $lng = $ntf->getUserLanguage($a_user_id);
4340 
4341  $ntf->setIntroductionLangId("svy_user_added_appraisee_close_mail");
4342  $subject = str_replace("%1", $this->getTitle(), $lng->txt("svy_user_added_appraisee"));
4343 
4344  // #10044
4345  $mail = new ilMail(ANONYMOUS_USER_ID);
4346  $mail->enqueue(
4347  ilObjUser::_lookupLogin($a_user_id),
4348  "",
4349  "",
4350  $subject,
4351  $ntf->composeAndGetMessage($a_user_id, null, "read", true),
4352  []
4353  );
4354  }
4355 
4359  public function sendRaterNotification(
4360  int $a_user_id,
4361  int $a_appraisee_id
4362  ): void {
4363  $ntf = new ilSystemNotification();
4364  $ntf->setLangModules(array("svy", "survey"));
4365  $ntf->setRefId($this->getRefId());
4366  $ntf->setGotoLangId('url');
4367 
4368  // user specific language
4369  $lng = $ntf->getUserLanguage($a_user_id);
4370 
4371  $ntf->setIntroductionLangId("svy_user_added_rater_mail");
4372  $subject = str_replace("%1", $this->getTitle(), $lng->txt("svy_user_added_rater"));
4373  $ntf->addAdditionalInfo("survey_360_appraisee", ilUserUtil::getNamePresentation($a_appraisee_id, false, false, "", true));
4374 
4375  // #10044
4376  $mail = new ilMail(ANONYMOUS_USER_ID);
4377  $mail->enqueue(
4378  ilObjUser::_lookupLogin($a_user_id),
4379  "",
4380  "",
4381  $subject,
4382  $ntf->composeAndGetMessage($a_user_id, null, "read", true),
4383  []
4384  );
4385  }
4386 
4390  public function isAppraisee(
4391  int $a_user_id
4392  ): bool {
4393  $ilDB = $this->db;
4394  $set = $ilDB->query("SELECT user_id" .
4395  " FROM svy_360_appr" .
4396  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
4397  " AND user_id = " . $ilDB->quote($a_user_id, "integer"));
4398  return (bool) $ilDB->numRows($set);
4399  }
4400 
4404  public function isAppraiseeClosed(
4405  int $a_user_id
4406  ): bool {
4407  $ilDB = $this->db;
4408 
4409  $set = $ilDB->query("SELECT has_closed" .
4410  " FROM svy_360_appr" .
4411  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
4412  " AND user_id = " . $ilDB->quote($a_user_id, "integer"));
4413  $row = $ilDB->fetchAssoc($set);
4414  return (bool) ($row["has_closed"] ?? false);
4415  }
4416 
4420  public function deleteAppraisee(
4421  int $a_user_id
4422  ): void {
4423  $ilDB = $this->db;
4424 
4425  $ilDB->manipulate("DELETE FROM svy_360_appr" .
4426  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
4427  " AND user_id = " . $ilDB->quote($a_user_id, "integer"));
4428 
4429  $set = $ilDB->query("SELECT user_id" .
4430  " FROM svy_360_rater" .
4431  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
4432  " AND appr_id = " . $ilDB->quote($a_user_id, "integer"));
4433  while ($row = $ilDB->fetchAssoc($set)) {
4434  $this->deleteRater($a_user_id, $row["user_id"]);
4435  }
4436  // appraisee will not be part of raters table
4437  if ($this->get360SelfEvaluation()) {
4438  $this->deleteRater($a_user_id, $a_user_id);
4439  }
4440  }
4441 
4445  public function getAppraiseesData(): array
4446  {
4447  $ilDB = $this->db;
4448 
4449  $res = array();
4450 
4451  $set = $ilDB->query("SELECT * FROM svy_360_appr" .
4452  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer"));
4453  while ($row = $ilDB->fetchAssoc($set)) {
4454  $name = ilObjUser::_lookupName($row["user_id"]);
4455  $name["email"] = ilObjUser::_lookupEmail($row["user_id"]);
4456  $name["name"] = $name["lastname"] . ", " . $name["firstname"];
4457  $res[$row["user_id"]] = $name;
4458 
4459  $finished = 0;
4460  $raters = $this->getRatersData($row["user_id"]);
4461  foreach ($raters as $rater) {
4462  if ($rater["finished"]) {
4463  $finished++;
4464  }
4465  }
4466  $res[$row["user_id"]]["finished"] = $finished . "/" . count($raters);
4467  $res[$row["user_id"]]["closed"] = $row["has_closed"];
4468  }
4469 
4470  return $res;
4471  }
4472 
4476  public function addRater(
4477  int $a_appraisee_id,
4478  int $a_user_id,
4479  int $a_anonymous_id = 0
4480  ): void {
4481  global $DIC;
4482 
4483  $ilDB = $DIC->database();
4484  $access = $DIC->access();
4485 
4486  if ($this->isAppraisee($a_appraisee_id) &&
4487  !$this->isRater($a_appraisee_id, $a_user_id, $a_anonymous_id)) {
4488  $fields = array(
4489  "obj_id" => array("integer", $this->getSurveyId()),
4490  "appr_id" => array("integer", $a_appraisee_id),
4491  "user_id" => array("integer", $a_user_id),
4492  "anonymous_id" => array("integer", $a_anonymous_id)
4493  );
4494  $ilDB->insert("svy_360_rater", $fields);
4495 
4496  // send notification and add to desktop
4497  if ($access->checkAccessOfUser($a_user_id, "read", "", $this->getRefId())) {
4498  // out-commented, since adding raters will end in a mail
4499  // form to send the mail "manually"
4500  // otherwise two mails would be sent (tested in individual feedback)
4501  //$this->sendRaterNotification($a_user_id, $a_appraisee_id);
4502  }
4503  }
4504  }
4505 
4509  public function isRater(
4510  int $a_appraisee_id,
4511  int $a_user_id,
4512  int $a_anonymous_id = 0
4513  ): bool {
4514  $ilDB = $this->db;
4515 
4516  // user is rater if already appraisee and active self-evaluation
4517  if ($this->isAppraisee($a_user_id) &&
4518  $this->get360SelfEvaluation() &&
4519  (!$a_appraisee_id || $a_appraisee_id === $a_user_id)) {
4520  return true;
4521  }
4522 
4523  // :TODO: should we get rid of code as well?
4524 
4525  $sql = "SELECT user_id" .
4526  " FROM svy_360_rater" .
4527  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
4528  " AND user_id = " . $ilDB->quote($a_user_id, "integer") .
4529  " AND anonymous_id = " . $ilDB->quote($a_anonymous_id, "integer");
4530  if ($a_appraisee_id) {
4531  $sql .= " AND appr_id = " . $ilDB->quote($a_appraisee_id, "integer");
4532  }
4533  $set = $ilDB->query($sql);
4534  return (bool) $ilDB->numRows($set);
4535  }
4536 
4540  public function deleteRater(
4541  int $a_appraisee_id,
4542  int $a_user_id,
4543  int $a_anonymous_id = 0
4544  ): void {
4545  $ilDB = $this->db;
4546 
4547  $finished_id = $this->getFinishedIdForAppraiseeIdAndRaterId($a_appraisee_id, $a_user_id);
4548  if ($finished_id) {
4549  $this->removeSelectedSurveyResults(array($finished_id));
4550  }
4551 
4552  $ilDB->manipulate("DELETE FROM svy_360_rater" .
4553  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
4554  " AND appr_id = " . $ilDB->quote($a_appraisee_id, "integer") .
4555  " AND user_id = " . $ilDB->quote($a_user_id, "integer") .
4556  " AND anonymous_id = " . $ilDB->quote($a_anonymous_id, "integer"));
4557  }
4558 
4562  public function getRatersData(
4563  int $a_appraisee_id
4564  ): array {
4565  $ilDB = $this->db;
4566 
4567  $res = $anonymous_ids = array();
4568 
4569  $set = $ilDB->query("SELECT * FROM svy_360_rater" .
4570  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
4571  " AND appr_id = " . $ilDB->quote($a_appraisee_id, "integer"));
4572  while ($row = $ilDB->fetchAssoc($set)) {
4573  if ($row["anonymous_id"]) {
4574  $res["a" . $row["anonymous_id"]] = array(
4575  "lastname" => "unknown code " . $row["anonymous_id"],
4576  "sent" => $row["mail_sent"],
4577  "finished" => null
4578  );
4579  $anonymous_ids[] = $row["anonymous_id"];
4580  } else {
4581  $name = ilObjUser::_lookupName($row["user_id"]);
4582  $name["name"] = $name["lastname"] . ", " . $name["firstname"];
4583  $name["user_id"] = "u" . $name["user_id"];
4584  $name["email"] = ilObjUser::_lookupEmail($row["user_id"]);
4585  $name["sent"] = $row["mail_sent"];
4586  $name["finished"] = (bool) $this->is360SurveyStarted($a_appraisee_id, (int) $row["user_id"]);
4587  $res["u" . $row["user_id"]] = $name;
4588  }
4589  }
4590 
4591  if (count($anonymous_ids)) {
4592  $data = $this->getSurveyCodesTableData($anonymous_ids);
4593  foreach ($data as $item) {
4594  if (isset($res["a" . $item["id"]])) {
4595  $res["a" . $item["id"]] = array(
4596  "user_id" => "a" . $item["id"],
4597  "lastname" => $item["last_name"],
4598  "firstname" => $item["first_name"],
4599  "name" => $item["last_name"] . ", " . $item["first_name"],
4600  "login" => "",
4601  "email" => $item["email"],
4602  "code" => $item["code"],
4603  "href" => $item["href"],
4604  "sent" => $res["a" . $item["id"]]["sent"],
4605  "finished" => (bool) $this->is360SurveyStarted($a_appraisee_id, 0, $item["code"])
4606  );
4607  }
4608  }
4609  }
4610 
4611  return $res;
4612  }
4613 
4618  public function getAppraiseesToRate(
4619  ?int $a_user_id,
4620  ?int $a_anonymous_id = null
4621  ): array {
4622  $ilDB = $this->db;
4623 
4624  $res = array();
4625 
4626  $sql = "SELECT appr_id FROM svy_360_rater" .
4627  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer");
4628 
4629  if ($a_user_id) {
4630  $sql .= " AND user_id = " . $ilDB->quote($a_user_id, "integer");
4631  } else {
4632  $sql .= " AND anonymous_id = " . $ilDB->quote($a_anonymous_id, "integer");
4633  }
4634 
4635  $set = $ilDB->query($sql);
4636  while ($row = $ilDB->fetchAssoc($set)) {
4637  $res[] = (int) $row["appr_id"];
4638  }
4639 
4640  // user may evaluate himself if already appraisee
4641  if ($this->get360SelfEvaluation() &&
4642  $this->isAppraisee((int) $a_user_id) &&
4643  !in_array($a_user_id, $res)) {
4644  $res[] = $a_user_id;
4645  }
4646 
4647  return $res;
4648  }
4649 
4653  public function getAnonymousIdByCode(
4654  string $a_code
4655  ): ?int {
4656  $ilDB = $this->db;
4657  $set = $ilDB->query("SELECT anonymous_id FROM svy_anonymous" .
4658  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
4659  " AND survey_key = " . $ilDB->quote($a_code, "text"));
4660  $res = $ilDB->fetchAssoc($set);
4661  return $res["anonymous_id"] ?? null;
4662  }
4663 
4667  public function is360SurveyStarted(
4668  int $appr_id,
4669  int $user_id,
4670  ?string $anonymous_code = null
4671  ): ?int {
4672  $ilDB = $this->db;
4673 
4674  $sql = "SELECT * FROM svy_finished" .
4675  " WHERE survey_fi =" . $ilDB->quote($this->getSurveyId(), "integer") .
4676  " AND appr_id = " . $ilDB->quote($appr_id, "integer");
4677  if ($user_id) {
4678  $sql .= " AND user_fi = " . $ilDB->quote($user_id, "integer");
4679  } else {
4680  $sql .= " AND anonymous_id = " . $ilDB->quote($anonymous_code, "text");
4681  }
4682  $result = $ilDB->query($sql);
4683  if ($result->numRows() === 0) {
4684  return null;
4685  } else {
4686  $row = $ilDB->fetchAssoc($result);
4687  return (int) $row["state"];
4688  }
4689  }
4690 
4700  ?string $a_code = null
4701  ): ?array {
4702  $ilUser = $this->user;
4703  $ilDB = $this->db;
4704  $user_id = $ilUser->getId();
4705  // code is obligatory?
4706  if (!$this->isAccessibleWithoutCode()) {
4707  if (!$a_code) {
4708  // registered raters do not need code
4709  if ($this->feature_config->usesAppraisees() &&
4711  $this->isRater(0, $user_id)) {
4712  // auto-generate code
4713  $code = $this->data_manager->code("")
4714  ->withUserId($user_id);
4715  $this->code_manager->add($code);
4716  $a_code = $this->code_manager->getByUserId($user_id);
4717  } else {
4718  return null;
4719  }
4720  }
4721  } elseif ($user_id === ANONYMOUS_USER_ID ||
4722  $this->getAnonymize() === self::ANONYMIZE_FREEACCESS) {
4723  // self::ANONYMIZE_FREEACCESS: anonymized, no codes
4724  // or anonymous user when no codes are used
4725  if (!$a_code) {
4726  // auto-generate code
4727  $code = $this->data_manager->code("")
4728  ->withUserId($user_id);
4729  $code_id = $this->code_manager->add($code);
4730  $a_code = $this->code_manager->getByCodeId($code_id);
4731  }
4732  } else {
4733  $a_code = null;
4734  }
4735 
4736  $res = array();
4737 
4738  // get runs for user id / code
4739  $sql = "SELECT * FROM svy_finished" .
4740  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer");
4741  // if proper user id is given, use it or current code
4742  if ($user_id !== ANONYMOUS_USER_ID) {
4743  $sql .= " AND (user_fi = " . $ilDB->quote($user_id, "integer") .
4744  " OR anonymous_id = " . $ilDB->quote($a_code, "text") . ")";
4745  }
4746  // use anonymous code to find finished id(s)
4747  else {
4748  $sql .= " AND anonymous_id = " . $ilDB->quote($a_code, "text");
4749  }
4750  $set = $ilDB->query($sql);
4751  while ($row = $ilDB->fetchAssoc($set)) {
4752  $res[$row["finished_id"]] = array("appr_id" => $row["appr_id"],
4753  "user_id" => $row["user_fi"],
4754  "code" => $row["anonymous_id"],
4755  "finished" => (bool) $row["state"]);
4756  }
4757  return array("code" => $a_code, "runs" => $res);
4758  }
4759 
4763  public function findCodeForUser(
4764  int $a_user_id
4765  ): string {
4766  $ilDB = $this->db;
4767 
4768  if ($a_user_id !== ANONYMOUS_USER_ID) {
4769  $set = $ilDB->query("SELECT sf.anonymous_id FROM svy_finished sf" .
4770  " WHERE sf.survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
4771  " AND sf.user_fi = " . $ilDB->quote($a_user_id, "integer"));
4772  $a_code = $ilDB->fetchAssoc($set);
4773  return (string) ($a_code["anonymous_id"] ?? "");
4774  }
4775  return "";
4776  }
4777 
4781  public function isUnusedCode(
4782  string $a_code,
4783  int $a_user_id
4784  ): bool {
4785  $ilDB = $this->db;
4786 
4787  $set = $ilDB->query("SELECT user_fi FROM svy_finished" .
4788  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
4789  " AND anonymous_id = " . $ilDB->quote($a_code, "text"));
4790  $user_id = $ilDB->fetchAssoc($set);
4791  $user_id = (int) $user_id["user_fi"];
4792 
4793  if ($user_id && ($user_id !== $a_user_id || $user_id === ANONYMOUS_USER_ID)) {
4794  return false;
4795  }
4796  return true;
4797  }
4798 
4804  int $a_appr_id,
4805  bool $a_exclude_appraisee = false
4806  ): array {
4807  $ilDB = $this->db;
4808 
4809  $res = array();
4810 
4811  $set = $ilDB->query("SELECT finished_id, user_fi FROM svy_finished" .
4812  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
4813  " AND appr_id = " . $ilDB->quote($a_appr_id, "integer"));
4814  while ($row = $ilDB->fetchAssoc($set)) {
4815  if ($a_exclude_appraisee && $row["user_fi"] == $a_appr_id) {
4816  continue;
4817  }
4818  $res[] = (int) $row["finished_id"];
4819  }
4820 
4821  return $res;
4822  }
4823 
4829  int $a_appr_id,
4830  int $a_rat_id
4831  ): ?int {
4832  $ilDB = $this->db;
4833 
4834  $set = $ilDB->query("SELECT finished_id, user_fi FROM svy_finished" .
4835  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
4836  " AND appr_id = " . $ilDB->quote($a_appr_id, "integer") .
4837  " AND user_fi = " . $ilDB->quote($a_rat_id, "integer"));
4838  if ($row = $ilDB->fetchAssoc($set)) {
4839  return (int) $row["finished_id"];
4840  }
4841  return null;
4842  }
4843 
4844 
4845  // 360° using competence/skill service
4846 
4847  public function setSkillService(bool $a_val): void
4848  {
4849  $this->mode_skill_service = $a_val;
4850  }
4851 
4852  public function getSkillService(): bool
4853  {
4855  }
4856 
4860  public function set360RaterSent(
4861  int $a_appraisee_id,
4862  int $a_user_id,
4863  int $a_anonymous_id,
4864  ?int $a_tstamp = null
4865  ): void {
4866  $ilDB = $this->db;
4867 
4868  if (!$a_tstamp) {
4869  $a_tstamp = time();
4870  }
4871 
4872  $ilDB->manipulate("UPDATE svy_360_rater" .
4873  " SET mail_sent = " . $ilDB->quote($a_tstamp, "integer") .
4874  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
4875  " AND appr_id = " . $ilDB->quote($a_appraisee_id, "integer") .
4876  " AND user_id = " . $ilDB->quote($a_user_id, "integer") .
4877  " AND anonymous_id = " . $ilDB->quote($a_anonymous_id, "integer"));
4878  }
4879 
4883  public function closeAppraisee(
4884  int $a_user_id
4885  ): void {
4886  global $DIC;
4887 
4888  $ilDB = $DIC->database();
4889  $user = $DIC->user();
4890 
4891  // close the appraisee
4892  $ilDB->manipulate("UPDATE svy_360_appr" .
4893  " SET has_closed = " . $ilDB->quote(time(), "integer") .
4894  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
4895  " AND user_id = " . $ilDB->quote($a_user_id, "integer"));
4896 
4897  // write competences
4898  $skmg_set = new ilSkillManagementSettings();
4899  if ($this->getSkillService() && $skmg_set->isActivated()) {
4900  $sskill = new ilSurveySkill($this);
4901  $sskill->writeAndAddAppraiseeSkills($a_user_id);
4902  }
4903 
4904  // send notification
4905  if ($user->getId() !== $a_user_id) {
4906  $this->sendAppraiseeCloseNotification($a_user_id);
4907  }
4908  }
4909 
4913  public function openAllAppraisees(): void
4914  {
4915  $ilDB = $this->db;
4916 
4917  $ilDB->manipulate("UPDATE svy_360_appr" .
4918  " SET has_closed = " . $ilDB->quote(null, "integer") .
4919  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer"));
4920  }
4921 
4925  public static function validateExternalRaterCode(
4926  int $a_ref_id,
4927  string $a_code
4928  ): bool {
4930  global $DIC;
4931 
4932  $anonym_repo = $DIC->survey()
4933  ->internal()
4934  ->repo()
4935  ->execution()
4936  ->runSession();
4937 
4938  if (!$anonym_repo->isExternalRaterValidated($a_ref_id)) {
4939  $svy = new self($a_ref_id);
4940  $svy->loadFromDb();
4941 
4942  $domain_service = $DIC->survey()->internal()->domain();
4943  $code_manager = $domain_service->code($svy, 0);
4944  $feature_config = $domain_service->modeFeatureConfig($svy->getMode());
4945  $access_manager = $domain_service->access($a_ref_id, 0);
4946 
4947  if ($access_manager->canStartSurvey() &&
4948  $feature_config->usesAppraisees() &&
4949  $code_manager->exists($a_code)) {
4950  $anonymous_id = $svy->getAnonymousIdByCode($a_code);
4951  if ($anonymous_id) {
4952  if (count($svy->getAppraiseesToRate(null, $anonymous_id))) {
4953  $anonym_repo->setExternalRaterValidation($a_ref_id, true);
4954  return true;
4955  }
4956  }
4957  }
4958  $anonym_repo->setExternalRaterValidation($a_ref_id, false);
4959  return false;
4960  }
4961 
4962  return $anonym_repo->isExternalRaterValidated($a_ref_id);
4963  }
4964 
4965 
4966  //
4967  // reminder/notification
4968  //
4969 
4970  public function getReminderStatus(): bool
4971  {
4972  return $this->reminder_status;
4973  }
4974 
4975  public function setReminderStatus(bool $a_value): void
4976  {
4977  $this->reminder_status = $a_value;
4978  }
4979 
4980  public function getReminderStart(): ?ilDate
4981  {
4982  return $this->reminder_start;
4983  }
4984 
4985  public function setReminderStart(?ilDate $a_value = null): void
4986  {
4987  $this->reminder_start = $a_value;
4988  }
4989 
4990  public function getReminderEnd(): ?ilDate
4991  {
4992  return $this->reminder_end;
4993  }
4994 
4995  public function setReminderEnd(?ilDate $a_value = null): void
4996  {
4997  $this->reminder_end = $a_value;
4998  }
4999 
5000  public function getReminderFrequency(): int
5001  {
5003  }
5004 
5005  public function setReminderFrequency(int $a_value): void
5006  {
5007  $this->reminder_frequency = $a_value;
5008  }
5009 
5010  public function getReminderTarget(): int
5011  {
5012  return $this->reminder_target;
5013  }
5014 
5015  public function setReminderTarget(int $a_value): void
5016  {
5017  $this->reminder_target = $a_value;
5018  }
5019 
5020  public function getReminderLastSent(): ?string
5021  {
5023  }
5024 
5025  public function setReminderLastSent(?string $a_value): void
5026  {
5027  if ($a_value == "") {
5028  $a_value = null;
5029  }
5030  $this->reminder_last_sent = $a_value;
5031  }
5032 
5033  public function getReminderTemplate(
5034  bool $selectDefault = false
5035  ): ?int {
5036  if ($selectDefault) {
5037  $defaultTemplateId = 0;
5038  $this->getReminderMailTemplates($defaultTemplateId);
5039 
5040  if ($defaultTemplateId > 0) {
5041  return $defaultTemplateId;
5042  }
5043  }
5044 
5045  return $this->reminder_tmpl;
5046  }
5047 
5048  public function setReminderTemplate(?int $a_value): void
5049  {
5050  $this->reminder_tmpl = $a_value;
5051  }
5052 
5053  public function getTutorNotificationStatus(): bool
5054  {
5055  return $this->tutor_ntf_status;
5056  }
5057 
5061  public function setTutorNotificationStatus(bool $a_value): void
5062  {
5063  $this->tutor_ntf_status = $a_value;
5064  }
5065 
5069  public function getTutorNotificationRecipients(): array
5070  {
5072  }
5073 
5077  public function setTutorNotificationRecipients(array $a_value): void
5078  {
5079  $this->tutor_ntf_recipients = $a_value;
5080  }
5081 
5088  public function getTutorNotificationTarget(): int
5089  {
5090  return $this->tutor_ntf_target;
5091  }
5092 
5093  public function setTutorNotificationTarget(int $a_value): void
5094  {
5095  $this->tutor_ntf_target = $a_value;
5096  }
5097 
5098  public function getTutorResultsStatus(): bool
5099  {
5100  return $this->tutor_res_status;
5101  }
5102 
5103  public function setTutorResultsStatus(bool $a_value): void
5104  {
5105  $this->tutor_res_status = $a_value;
5106  }
5107 
5108  public function getTutorResultsRecipients(): array
5109  {
5111  }
5112 
5113  public function setTutorResultsRecipients(array $a_value): void
5114  {
5115  $this->tutor_res_recipients = $a_value;
5116  }
5117 
5122  protected function checkTutorNotification(): void
5123  {
5124  $ilDB = $this->db;
5125 
5126  if ($this->getTutorNotificationStatus()) {
5127  // get target users, either parent course/group members or
5128  // user with the survey on the dashboard
5129  $user_ids = $this->getNotificationTargetUserIds(($this->getTutorNotificationTarget() === self::NOTIFICATION_INVITED_USERS));
5130  if ($user_ids) {
5131  $set = $ilDB->query("SELECT COUNT(*) numall FROM svy_finished" .
5132  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
5133  " AND state = " . $ilDB->quote(1, "integer") .
5134  " AND " . $ilDB->in("user_fi", $user_ids, "", "integer"));
5135  $row = $ilDB->fetchAssoc($set);
5136 
5137  // all users finished the survey -> send notifications
5138  if ((int) $row["numall"] === count($user_ids)) {
5139  $this->sendTutorNotification();
5140  }
5141  }
5142  }
5143  }
5144 
5148  public function sent360Reminders(): void
5149  {
5150  global $DIC;
5151 
5152  $access = $DIC->access();
5153  // collect all open ratings
5154  $rater_ids = array();
5155  foreach ($this->getAppraiseesData() as $app) {
5156  $this->svy_log->debug("Handle appraisee " . $app['user_id']);
5157 
5158  if (!$this->isAppraiseeClosed($app['user_id'])) {
5159  $this->svy_log->debug("Check self evaluation, self: " . $this->get360SelfAppraisee() . ", target: " . $this->getReminderTarget());
5160 
5161  // self evaluation?
5162  if ($this->get360SelfEvaluation() &&
5163  in_array($this->getReminderTarget(), array(self::NOTIFICATION_APPRAISEES, self::NOTIFICATION_APPRAISEES_AND_RATERS))) {
5164  $this->svy_log->debug("...1");
5165  // did user already finished self evaluation?
5166  if (!$this->is360SurveyStarted((int) $app['user_id'], (int) $app['user_id'])) {
5167  $this->svy_log->debug("...2");
5168  if (!isset($rater_ids[$app['user_id']])) {
5169  $rater_ids[$app['user_id']] = array();
5170  }
5171  if (!isset($app["user_id"], $rater_ids[$app['user_id']])) {
5172  $rater_ids[$app['user_id']][] = $app["user_id"];
5173  }
5174  }
5175  }
5176 
5177  $this->svy_log->debug("Check raters.");
5178 
5179  // should raters be notified?
5180  if (
5181  in_array(
5182  $this->getReminderTarget(),
5183  array(self::NOTIFICATION_RATERS, self::NOTIFICATION_APPRAISEES_AND_RATERS),
5184  true
5185  )
5186  ) {
5187  foreach ($this->getRatersData($app['user_id']) as $rater) {
5188  $rater_id = 0;
5189  if ($rater["login"] !== "") {
5190  $rater_id = ilObjUser::_lookupId($rater["login"]);
5191  }
5192  if ($rater_id > 0) {
5193  // is rater not anonymous and did not rate yet?
5194  if (!($rater["anonymous_id"] ?? false) && !($rater["finished"] ?? false)) {
5195  if (!isset($rater_ids[$rater_id])) {
5196  $rater_ids[$rater_id] = array();
5197  }
5198  if (!in_array($app["user_id"], $rater_ids[$rater_id])) {
5199  $rater_ids[$rater_id][] = $app["user_id"];
5200  }
5201  }
5202  }
5203  }
5204  }
5205  }
5206  }
5207 
5208  $this->svy_log->debug("Found raters:" . count($rater_ids));
5209 
5210  foreach ($rater_ids as $id => $app) {
5211  if ($access->checkAccessOfUser((int) $id, "read", "", $this->getRefId())) {
5212  $this->send360ReminderToUser((int) $id, $app);
5213  }
5214  }
5215  }
5216 
5217  public function send360ReminderToUser(
5218  int $a_user_id,
5219  array $a_appraisee_ids
5220  ): void {
5221  $this->svy_log->debug("Send mail to:" . $a_user_id);
5222 
5223  $ntf = new ilSystemNotification();
5224  $ntf->setLangModules(array("svy", "survey"));
5225  $ntf->setRefId($this->getRefId());
5226  $ntf->setGotoLangId('url');
5227 
5228  // user specific language
5229  $lng = $ntf->getUserLanguage($a_user_id);
5230 
5231  $ntf->setIntroductionLangId("svy_user_added_rater_reminder_mail");
5232  $subject = str_replace("%1", $this->getTitle(), $lng->txt("svy_user_added_rater"));
5233 
5234  foreach ($a_appraisee_ids as $appraisee_id) {
5235  $ntf->addAdditionalInfo("survey_360_appraisee", ilUserUtil::getNamePresentation($appraisee_id, false, false, "", true));
5236  }
5237 
5238  // #10044
5239  $mail = new ilMail(ANONYMOUS_USER_ID);
5240  $mail->enqueue(
5241  ilObjUser::_lookupLogin($a_user_id),
5242  "",
5243  "",
5244  $subject,
5245  $ntf->composeAndGetMessage($a_user_id, null, "read", true),
5246  []
5247  );
5248  }
5249 
5258  bool $a_use_invited
5259  ): array {
5260  $tree = $this->tree;
5261 
5262  $user_ids = [];
5263  if ($a_use_invited) {
5264  $user_ids = $this->invitation_manager->getAllForSurvey($this->getSurveyId());
5265  } else {
5266  $parent_grp_ref_id = $tree->checkForParentType($this->getRefId(), "grp");
5267  if ($parent_grp_ref_id) {
5268  $part = new ilGroupParticipants(ilObject::_lookupObjId($parent_grp_ref_id));
5269  $user_ids = $part->getMembers();
5270  } else {
5271  $parent_crs_ref_id = $tree->checkForParentType($this->getRefId(), "crs");
5272  if ($parent_crs_ref_id) {
5273  $part = new ilCourseParticipants(ilObject::_lookupObjId($parent_crs_ref_id));
5274  $user_ids = $part->getMembers();
5275  }
5276  }
5277  }
5278  return $user_ids;
5279  }
5280 
5284  protected function sendTutorNotification(): void
5285  {
5286  $link = ilLink::_getStaticLink($this->getRefId(), "svy");
5287 
5288  // get tutors being set in the setting
5289  foreach ($this->getTutorNotificationRecipients() as $user_id) {
5290  // use language of recipient to compose message
5291  $ulng = ilLanguageFactory::_getLanguageOfUser($user_id);
5292  $ulng->loadLanguageModule('survey');
5293 
5294  $subject = sprintf($ulng->txt('survey_notification_tutor_subject'), $this->getTitle());
5295  $message = sprintf($ulng->txt('survey_notification_tutor_salutation'), ilObjUser::_lookupFullname($user_id)) . "\n\n";
5296 
5297  $message .= $ulng->txt('survey_notification_tutor_body') . ":\n\n";
5298  $message .= $ulng->txt('obj_svy') . ": " . $this->getTitle() . "\n";
5299  $message .= "\n" . $ulng->txt('survey_notification_tutor_link') . ": " . $link;
5300 
5301  $mail_obj = new ilMail(ANONYMOUS_USER_ID);
5302  $mail_obj->appendInstallationSignature(true);
5303  $mail_obj->enqueue(
5304  ilObjUser::_lookupLogin($user_id),
5305  "",
5306  "",
5307  $subject,
5308  $message,
5309  array()
5310  );
5311  }
5312  }
5313 
5314  public function checkReminder(): ?int
5315  {
5316  $ilDB = $this->db;
5317  $ilAccess = $this->access;
5318 
5319  $now = time();
5320  $now_with_format = date("YmdHis", $now);
5321  $today = date("Y-m-d");
5322 
5323  $this->svy_log->debug("Check status and dates.");
5324 
5325  // object settings / participation period
5326  if (
5327  $this->getOfflineStatus() ||
5328  !$this->getReminderStatus() ||
5329  ($this->getStartDate() && $now_with_format < $this->getStartDate()) ||
5330  ($this->getEndDate() && $now_with_format > $this->getEndDate())) {
5331  return null;
5332  }
5333 
5334  // reminder period
5335  $start = $this->getReminderStart();
5336  if ($start) {
5337  $start = $start->get(IL_CAL_DATE);
5338  }
5339  $end = $this->getReminderEnd();
5340  if ($end) {
5341  $end = $end->get(IL_CAL_DATE);
5342  }
5343  if ($today < $start ||
5344  ($end && $today > $end)) {
5345  return null;
5346  }
5347 
5348  $this->svy_log->debug("Check access period.");
5349 
5350  // object access period
5351  $item_data = ilObjectActivation::getItem($this->getRefId());
5352  if ((int) $item_data["timing_type"] === ilObjectActivation::TIMINGS_ACTIVATION &&
5353  ($now < $item_data["timing_start"] ||
5354  $now > $item_data["timing_end"])) {
5355  return null;
5356  }
5357 
5358  $this->svy_log->debug("Check frequency.");
5359 
5360  // check frequency
5361  $cut = new ilDate($today, IL_CAL_DATE);
5362  $cut->increment(IL_CAL_DAY, $this->getReminderFrequency() * -1);
5363  if (!$this->getReminderLastSent() ||
5364  $cut->get(IL_CAL_DATE) >= substr($this->getReminderLastSent(), 0, 10)) {
5365  $missing_ids = array();
5366  if (!$this->feature_config->usesAppraisees()) {
5367  $this->svy_log->debug("Entering survey mode.");
5368 
5369  // #16871
5370  $user_ids = $this->getNotificationTargetUserIds(($this->getReminderTarget() === self::NOTIFICATION_INVITED_USERS));
5371  if ($user_ids) {
5372  // gather participants who already finished
5373  $finished_ids = array();
5374  $set = $ilDB->query("SELECT user_fi FROM svy_finished" .
5375  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
5376  " AND state = " . $ilDB->quote(1, "text") .
5377  " AND " . $ilDB->in("user_fi", $user_ids, "", "integer"));
5378  while ($row = $ilDB->fetchAssoc($set)) {
5379  $finished_ids[] = $row["user_fi"];
5380  }
5381 
5382  // some users missing out?
5383  $missing_ids = array_diff($user_ids, $finished_ids);
5384  if ($missing_ids) {
5385  foreach ($missing_ids as $idx => $user_id) {
5386  // should be able to participate
5387  if (!$ilAccess->checkAccessOfUser($user_id, "read", "", $this->getRefId(), "svy", $this->getId())) {
5388  unset($missing_ids[$idx]);
5389  }
5390  }
5391  }
5392  if ($missing_ids) {
5393  $this->sentReminder($missing_ids);
5394  }
5395  }
5396  } else {
5397  $this->svy_log->debug("Entering 360 mode.");
5398 
5399  $this->sent360Reminders();
5400  }
5401 
5402 
5403  $this->setReminderLastSent($today);
5404  $this->saveToDb();
5405 
5406  return count($missing_ids);
5407  }
5408 
5409  return null;
5410  }
5411 
5412  protected function sentReminder(
5413  array $a_recipient_ids
5414  ): void {
5415  global $DIC;
5416 
5417  $link = "";
5418 
5419  // use mail template
5420  if ($this->getReminderTemplate() &&
5421  array_key_exists($this->getReminderTemplate(), $this->getReminderMailTemplates())) {
5423  $templateService = $DIC->mail()->textTemplates();
5424  $tmpl = $templateService->loadTemplateForId($this->getReminderTemplate());
5425 
5426  $tmpl_params = array(
5427  "ref_id" => $this->getRefId(),
5428  "ts" => time()
5429  );
5430  } else {
5431  $tmpl = null;
5432  $tmpl_params = null;
5433  $link = ilLink::_getStaticLink($this->getRefId(), "svy");
5434  }
5435 
5436  foreach ($a_recipient_ids as $user_id) {
5437  if ($tmpl) {
5438  $subject = $tmpl->getSubject();
5439  $message = $this->sentReminderPlaceholders($tmpl->getMessage(), $user_id, $tmpl_params);
5440  }
5441  // use lng
5442  else {
5443  // use language of recipient to compose message
5444  $ulng = ilLanguageFactory::_getLanguageOfUser($user_id);
5445  $ulng->loadLanguageModule('survey');
5446 
5447  $subject = sprintf($ulng->txt('survey_reminder_subject'), $this->getTitle());
5448  $message = sprintf($ulng->txt('survey_reminder_salutation'), ilObjUser::_lookupFullname($user_id)) . "\n\n";
5449 
5450  $message .= $ulng->txt('survey_reminder_body') . ":\n\n";
5451  $message .= $ulng->txt('obj_svy') . ": " . $this->getTitle() . "\n";
5452  $message .= "\n" . $ulng->txt('survey_reminder_link') . ": " . $link;
5453  }
5454 
5455  $mail_obj = new ilMail(ANONYMOUS_USER_ID);
5456  $mail_obj->appendInstallationSignature(true);
5457  $mail_obj->enqueue(
5458  ilObjUser::_lookupLogin($user_id),
5459  "",
5460  "",
5461  $subject,
5462  $message,
5463  array()
5464  );
5465  }
5466  }
5467 
5468  public function setActivationStartDate(
5469  ?int $starting_time = null
5470  ): void {
5471  $this->activation_starting_time = $starting_time;
5472  }
5473 
5474  public function setActivationEndDate(
5475  ?int $ending_time = null
5476  ): void {
5477  $this->activation_ending_time = $ending_time;
5478  }
5479 
5480  public function getActivationStartDate(): ?int
5481  {
5483  }
5484 
5485  public function getActivationEndDate(): ?int
5486  {
5488  }
5489 
5490  public function setViewOwnResults(bool $a_value): void
5491  {
5492  $this->view_own_results = $a_value;
5493  }
5494 
5495  public function hasViewOwnResults(): bool
5496  {
5497  return $this->view_own_results;
5498  }
5499 
5500  public function setMailOwnResults(bool $a_value): void
5501  {
5502  $this->mail_own_results = $a_value;
5503  }
5504 
5505  public function hasMailOwnResults(): bool
5506  {
5507  return $this->mail_own_results;
5508  }
5509 
5510  public function setMailConfirmation(bool $a_value): void
5511  {
5512  $this->mail_confirmation = $a_value;
5513  }
5514 
5515  public function hasMailConfirmation(): bool
5516  {
5517  return $this->mail_confirmation;
5518  }
5519 
5520  public function setAnonymousUserList(bool $a_value): void
5521  {
5522  $this->anon_user_list = $a_value;
5523  }
5524 
5525  public function hasAnonymousUserList(): bool
5526  {
5527  return $this->anon_user_list;
5528  }
5529 
5530  public static function getSurveySkippedValue(): string
5531  {
5532  global $DIC;
5533 
5534  $lng = $DIC->language();
5535 
5536  // #13541
5537 
5538  $surveySetting = new ilSetting("survey");
5539  if (!$surveySetting->get("skipped_is_custom", false)) {
5540  return $lng->txt("skipped");
5541  } else {
5542  return $surveySetting->get("skipped_custom_value", "");
5543  }
5544  }
5545 
5546  public function getReminderMailTemplates(
5547  ?int &$defaultTemplateId = null
5548  ): array {
5549  global $DIC;
5550 
5551  $res = array();
5552 
5553  $templateService = $DIC->mail()->textTemplates();
5554  foreach ($templateService->loadTemplatesForContextId(ilSurveyMailTemplateReminderContext::ID) as $tmpl) {
5555  $res[$tmpl->getTplId()] = $tmpl->getTitle();
5556  if (null !== $defaultTemplateId && $tmpl->isDefault()) {
5557  $defaultTemplateId = $tmpl->getTplId();
5558  }
5559  }
5560 
5561  return $res;
5562  }
5563 
5564  protected function sentReminderPlaceholders(
5565  string $a_message,
5566  int $a_user_id,
5567  array $a_context_params
5568  ): string {
5569  // see ilMail::replacePlaceholders()
5570  try {
5572 
5573  $user = new \ilObjUser($a_user_id);
5574 
5575  $a_message = $this->placeholder_resolver->resolve($context, $a_message, $user, $a_context_params);
5576  } catch (\Exception $e) {
5577  ilLoggerFactory::getLogger('mail')->error(__METHOD__ . ' has been called with invalid context.');
5578  }
5579 
5580  return $a_message;
5581  }
5582 
5583  public function setMode(int $a_value): void
5584  {
5585  $this->mode = $a_value;
5586  }
5587 
5588  public function getMode(): int
5589  {
5590  return $this->mode;
5591  }
5592 
5593  public function setSelfEvaluationResults(int $a_value): void
5594  {
5595  $this->mode_self_eval_results = $a_value;
5596  }
5597 
5598  public function getSelfEvaluationResults(): int
5599  {
5601  }
5602 
5606  public static function getSurveysWithTutorResults(): array
5607  {
5608  global $ilDB;
5609 
5610  $res = array();
5611 
5613 
5614 
5615  $q = "SELECT obj_fi FROM svy_svy" .
5616  " WHERE tutor_res_cron IS NULL" .
5617  " AND tutor_res_status = " . $ilDB->quote(1, "integer") .
5618  " AND enddate < " . $ilDB->quote(date("Ymd000000"), "text");
5619 
5620  if (DEVMODE) {
5621  $q = "SELECT obj_fi FROM svy_svy" .
5622  " WHERE tutor_res_status = " . $ilDB->quote(1, "integer") .
5623  " AND enddate < " . $ilDB->quote(date("Ymd000000"), "text");
5624  }
5625 
5626  $set = $ilDB->query($q);
5627 
5628  $log->debug($q);
5629 
5630  while ($row = $ilDB->fetchAssoc($set)) {
5631  $res[] = (int) $row["obj_fi"];
5632  }
5633 
5634  return $res;
5635  }
5636 
5637  public function getMaxSumScore(): int
5638  {
5639  $sum_score = 0;
5641  $sum_score += call_user_func([$c, "getMaxSumScore"], $this->getSurveyId());
5642  }
5643  return $sum_score;
5644  }
5645 }
deleteUserSettings(int $id)
static _replaceMediaObjectImageSrc(string $a_text, int $a_direction=0, string $nic='')
Replaces image source from mob image urls with the mob id or replaces mob id with the correct image s...
setTutorResultsRecipients(array $a_value)
getTutorNotificationTarget()
Group that is checked for the "all participants have finished" mail Either a) all members of a parent...
removeQuestions(array $remove_questions, array $remove_questionblocks)
string $title
setIntroduction(string $introduction="")
importSurveyCode(string $a_anonymize_key, int $a_created, array $a_data)
setReminderEnd(?ilDate $a_value=null)
updateConjunctionForQuestions(array $questions, int $conjunction)
getSurveyQuestions(bool $with_answers=false)
Returns the survey questions and questionblocks in an array.
isAnonymizedParticipant(string $key)
setReminderFrequency(int $a_value)
getLastAccess(int $finished_id)
$res
Definition: ltiservices.php:66
ILIAS Survey Code CodeManager $code_manager
checkConstraint(array $constraint_data, ?array $working_data)
Checks if a constraint is valid.
isSurveyFinishedByCode(string $a_code)
Get if survey is finished for a specific anonymous user code.
setMailParticipantData(string $a_data)
Set preceding text (incl.
ILIAS SurveyQuestionPool Export ImportManager $import_manager
string $reminder_last_sent
setEndDate(string $end_date="")
isAppraisee(int $a_user_id)
$context
Definition: webdav.php:31
getMailParticipantData()
Preceding text (incl.
const IL_INST_ID
Definition: constants.php:40
const IL_CAL_DATETIME
string $mailparticipantdata
manipulateF(string $query, array $types, array $values)
setSurveyId(int $survey_id)
addAppraisee(int $a_user_id)
const ANONYMOUS_USER_ID
Definition: constants.php:27
saveUserAccessCode(int $user_id, string $access_code)
Saves a survey access code for a registered user to the database.
static getLogger(string $a_component_id)
Get component logger.
setShowQuestionTitles(bool $a_show)
static _hasDatasets(int $survey_id)
setObligatoryStates(array $obligatory_questions)
Sets the obligatory states for questions in a survey from the questions form.
$relation
Mode FeatureConfig $feature_config
const EVALUATION_ACCESS_OFF
txt(string $a_topic, string $a_default_lang_fallback_mod="")
gets the text for a given topic if the topic is not in the list, the topic itself with "-" will be re...
setAnonymize(int $a_anonymize)
set anonymize status
getSurveyPages()
Returns the survey pages in an array (a page contains one or more questions)
getUserLanguage()
Return language of user.
int $display_question_titles
getTextblock(int $question_id)
const EVALUATION_ACCESS_PARTICIPANTS
getSurveyCodesTableData(?array $ids=null, ?string $lang=null)
Fetches the data for the survey codes table.
static _lookupFullname(int $a_user_id)
getVariables(int $question_id)
Returns all variables (answer options/scale values) of a question.
setReminderTemplate(?int $a_value)
is360SurveyStarted(int $appr_id, int $user_id, ?string $anonymous_code=null)
getExistingQuestions()
Gets the question id&#39;s of the questions which are already in the survey.
if(! $DIC->user() ->getId()||!ilLTIConsumerAccess::hasCustomProviderCreationAccess()) $params
Definition: ltiregstart.php:31
static getSurveySkippedValue()
string $author
A text representation of the authors name.
isComplete()
Check if survey is complete for use.
static stripSlashes(string $a_str, bool $a_strip_html=true, string $a_allow="")
addConstraint(int $if_question_id, int $relation, float $value, int $conjunction)
Adds a constraint.
setEndDateAndTime(string $end_date, string $end_time)
setMailNotification(bool $a_notification)
Activate mail to tutors each time a participant finishes the survey.
duplicateQuestionForSurvey(int $question_id, bool $a_force=false)
Takes a question and creates a copy of the question for use in the survey.
setSelfEvaluationResults(int $a_value)
static _getQuestionblock(int $questionblock_id)
get question block properties
getAppraiseesToRate(?int $a_user_id, ?int $a_anonymous_id=null)
ilTree $tree
const NOTIFICATION_APPRAISEES
getQuestionblocksTable(array $arrFilter)
Retrieve data for question block browser.
getUserAccessCode(int $user_id)
Returns a survey access code that was saved for a registered user.
static _lookupName(int $a_user_id)
lookup user name
getFinishedIdsForAppraiseeId(int $a_appr_id, bool $a_exclude_appraisee=false)
findCodeForUser(int $a_user_id)
static _getAvailableQuestionpools(bool $use_object_id=false, bool $could_be_offline=false, bool $showPath=false, string $permission="read")
Returns the available question pools for the active user.
moveQuestions(array $move_questions, int $target_index, int $insert_mode)
Move questions and/or questionblocks to another position.
Import class.
static _lookupId($a_user_str)
loadWorkingData(int $question_id, int $active_id)
Gets the given answer data of a question in a run.
getFullname(int $a_max_strlen=0)
addConstraintToQuestion(int $to_question_id, int $constraint_id)
create($a_upload=false)
static getUserIdsByEmail(string $a_email)
static _getQuestionType(int $question_id)
Returns the question type of a question with a given id.
$url
Definition: shib_logout.php:66
setMailConfirmation(bool $a_value)
getSurveyParticipants(?array $finished_ids=null, bool $force_non_anonymous=false, bool $include_invites=false)
set360Results(int $a_value)
deleteAppraisee(int $a_user_id)
setMailAddresses(string $a_addresses)
Set (Tutor) Recipients of "single participant has finished" mails.
static _addQuestionblock(string $title="", int $owner=0, bool $show_questiontext=true, bool $show_blocktitle=false, bool $compress_view=false)
deleteAllUserData(bool $reset_LP=true)
Deletes all user data of a survey.
const IL_CAL_UNIX
getQuestionpoolTitles(bool $could_be_offline=false, bool $showPath=false)
Returns the available question pools for the active user.
setAuthor(string $author="")
int $survey_id
A unique positive numerical ID which identifies the survey.
isUnusedCode(string $a_code, int $a_user_id)
$c
Definition: deliver.php:25
static makeDirParents(string $a_dir)
Create a new directory and all parent directories.
checkTutorNotification()
Check, if mail to tutors after all participants have finished the survey should be sent...
setActivationStartDate(?int $starting_time=null)
removeSelectedSurveyResults(array $finished_ids)
Deletes the user data of a given array of survey participants.
getReminderTemplate(bool $selectDefault=false)
setActivationVisibility(bool $a_value)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
canExportSurveyCode()
Checks if the survey code can be exported with the survey evaluation.
insertQuestionblock(int $questionblock_id)
sort()
description: > Example for rendering a Sort Glyph.
Definition: sort.php:41
sendTutorNotification()
Send mail to tutors after all participants have finished the survey.
sentReminderPlaceholders(string $a_message, int $a_user_id, array $a_context_params)
getReminderMailTemplates(?int &$defaultTemplateId=null)
setTutorNotificationStatus(bool $a_value)
Activates mail being sent to tutors after all participants have finished the survey.
static _lookupObjId(int $ref_id)
prepareTextareaOutput(string $txt_output)
Prepares a string for a text area output in surveys.
importObject(array $file_info, int $svy_qpl_id)
unfoldQuestionblocks(array $questionblocks)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
xmlEndTag(string $tag)
Writes an endtag.
setViewOwnResults(bool $a_value)
static is_email(string $a_email, ?ilMailRfc822AddressParserFactory $mailAddressParserFactory=null)
This preg-based function checks whether an e-mail address is formally valid.
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
setOutro(string $outro="")
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getQuestionblockQuestions(int $questionblock_id)
setPage(int $finished_id, int $page_id)
Sets the number of the active survey page.
setReminderTarget(int $a_value)
getNextPage(int $active_page_question_id, int $direction)
Get current, previous or next page.
getTutorNotificationRecipients()
Mail being sent to tutors after all participants have finished the survey?
getParticipantTextResults(int $active_id)
InternalDomainService $domain
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getQuestionsTable(array $arrFilter)
Retrieve data for question browser.
cloneTextblocks(array $mapping)
Clones the text blocks of survey questions.
const IL_CAL_DAY
static _getQuestionGUI(?string $questiontype, int $question_id=-1)
Creates a question gui representation.
addQuestion(int $question_id)
Adds a question to the survey (used in importer!)
deleteSurveyRecord()
Deletes the survey from the database.
getWorkingtimeForParticipant(int $finished_id)
setPoolUsage(bool $a_value)
getEvaluationByUser(array $questions, int $active_id)
Calculates the evaluation data for a given run.
ILIAS Survey InternalService $survey_service
checkForParentType(int $a_ref_id, string $a_type, bool $a_exclude_source_check=false)
Check for parent type e.g check if a folder (ref_id 3) is in a parent course obj => checkForParentTyp...
static _isComplete(int $question_id)
Checks whether the question is complete or not.
setMailOwnResults(bool $a_value)
getMailNotification()
Send mail to tutors each time a participant finishes the survey?
cloneMetaData(ilObject $target_obj)
Copy meta data.
update($a_upload=false)
static _instanciateQuestionEvaluation(int $question_id, ?array $a_finished_ids=null)
isSurveyCodeUsed(string $code)
sendAppraiseeCloseNotification(int $a_user_id)
static getNamePresentation( $a_user_id, bool $a_user_image=false, bool $a_profile_link=false, string $a_profile_back_link='', bool $a_force_first_lastname=false, bool $a_omit_login=false, bool $a_sortable=true, bool $a_return_data_array=false, $a_ctrl_path='ilpublicuserprofilegui')
Default behaviour is:
static _lookupTitle(int $obj_id)
getPrecondition(int $constraint_id)
Returns a precondition with a given id.
updateCode(int $a_id, string $a_email, string $a_last_name, string $a_first_name, int $a_sent)
isQuestionInSurvey(int $a_question_fi)
string $evaluation_access
ilAccessHandler $access
sendAppraiseeNotification(int $a_user_id)
set360RaterSent(int $a_appraisee_id, int $a_user_id, int $a_anonymous_id, ?int $a_tstamp=null)
const ANONYMIZE_FREEACCESS
static _getLanguageOfUser(int $a_usr_id)
Get language object of user.
static _getConstraints(int $survey_id)
const NOTIFICATION_PARENT_COURSE
setCalculateSumScore(bool $a_val)
static _instanciateQuestion(int $question_id)
Creates an instance of a question with a given question id.
getConstraints(int $question_id)
Returns the constraints to a given question or questionblock.
const RESULTS_SELF_EVAL_OWN
setStartDateAndTime(string $start_date, string $start_time)
const NOTIFICATION_APPRAISEES_AND_RATERS
ilLanguage $lng
updateOrder(array $a_order)
getQuestionblockQuestionIds(int $questionblock_id)
getSurveyCodesForExport(?array $a_codes=null, ?array $a_ids=null)
Return a list of survey codes for file export, note: user_key needs to be null to export a record...
ilDBInterface $db
static delDir(string $a_dir, bool $a_clean_only=false)
removes a dir and all its content (subdirs and files) recursively
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
const QUESTIONTITLES_VISIBLE
getImportDirectory()
get import directory of survey
const QUESTIONTITLES_HIDDEN
modifyQuestionblock(int $questionblock_id, string $title, bool $show_questiontext, bool $show_blocktitle, bool $compress_view=false)
set360SelfRaters(bool $a_value)
global $DIC
Definition: shib_login.php:22
setStartDate(string $start_date="")
setTutorNotificationRecipients(array $a_value)
Set tutor recipients for "all participants have finished" mail.
static _getUserData(array $a_internalids)
return user data for given user ids
static _includeClass(string $question_type, int $gui=0)
Include the php class file for a given question type.
updateConstraint(int $precondition_id, int $if_question_id, int $relation, float $value, int $conjunction)
deleteSurveyCode(string $survey_code)
getUserSettings(int $usr_id, string $key)
toXML()
Returns a QTI xml representation of the survey.
saveAuthorToMetadata(string $a_author="")
Saves an authors name into the lifecycle metadata if no lifecycle metadata exists This will only be c...
static _getOriginalId(int $question_id, bool $a_return_question_id_if_no_original=true)
Returns the original id of a question.
getAnonymousIdByCode(string $a_code)
static _getUsedHTMLTagsAsString(string $a_module="")
Returns a string of all allowed HTML tags for text editing.
isHTML(string $a_text)
Checks if a given string contains HTML or not.
Participants InvitationsManager $invitation_manager
static moveUploadedFile(string $a_file, string $a_name, string $a_target, bool $a_raise_errors=true, string $a_mode="move_uploaded")
move uploaded file
static _getObjectsByOperations( $a_obj_type, string $a_operation, int $a_usr_id=0, int $limit=0)
Get all objects of a specific type and check access This function is not recursive, instead it parses the serialized rbac_pa entries.
getNotificationTargetUserIds(bool $a_use_invited)
These users must finish the survey to trigger the tutor notification "all users finished the survey" ...
setOfflineStatus(bool $status)
setTutorResultsStatus(bool $a_value)
static getDataDir()
get data directory (outside webspace)
const NOTIFICATION_INVITED_USERS
set360SelfEvaluation(bool $a_value)
getActiveID(int $user_id, string $anonymize_id, int $appr_id)
Get run id.
Class ilMailTemplatePlaceholderResolver.
static _getMobsOfObject(string $a_type, int $a_id, int $a_usage_hist_nr=0, string $a_lang="-")
getQuestionGUI(string $questiontype, int $question_id)
getAvailableQuestionpools(bool $use_obj_id=false, bool $could_be_offline=false, bool $showPath=false, string $permission="read")
Returns the available question pools for the active user.
static _removeUsage(int $a_mob_id, string $a_type, int $a_id, int $a_usage_hist_nr=0, string $a_lang="-")
Remove usage of mob in another container.
deleteConstraints(int $question_id)
Deletes the constraints for a question.
createImportDirectory()
creates data directory for import files (data_dir/svy_data/svy_<id>/import)
setReminderLastSent(?string $a_value)
createExportDirectory()
creates data directory for export files (data_dir/svy_data/svy_<id>/export)
isRater(int $a_appraisee_id, int $a_user_id, int $a_anonymous_id=0)
set360SelfAppraisee(bool $a_value)
ilMailTemplatePlaceholderResolver $placeholder_resolver
const EVALUATION_ACCESS_ALL
getUserData(array $ids)
Returns a data of all users specified by id list.
$lang
Definition: xapiexit.php:25
isSurveyCodeUnique(string $code)
saveCompletionStatus()
Saves the completion status of the survey.
removeQuestionFromBlock(int $question_id, int $questionblock_id)
getSurveyFinishedIds()
Get run ids.
static getSurveysWithTutorResults()
getExternalCodeRecipients(bool $a_check_finished=false)
getAllRelations(bool $short_as_key=false)
removeConstraintsConcerningQuestion(int $question_id)
Remove constraints concerning a question with a given question_id.
deleteRater(int $a_appraisee_id, int $a_user_id, int $a_anonymous_id=0)
const IL_CAL_DATE
static getItem(int $ref_id)
getQuestionType(int $question_id)
setTutorNotificationTarget(int $a_value)
isQuestionInAnyBlock(int $a_question_fi)
isAppraiseeClosed(int $a_user_id)
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
__construct(Container $dic, ilPlugin $plugin)
ilErrorHandling $error
setActivationLimited(bool $a_value)
addRater(int $a_appraisee_id, int $a_user_id, int $a_anonymous_id=0)
$q
Definition: shib_logout.php:21
array $tutor_ntf_recipients
const RESULTS_SELF_EVAL_NONE
setEvaluationAccess(string $evaluation_access=self::EVALUATION_ACCESS_OFF)
addQuestionToBlock(int $question_id, int $questionblock_id)
xmlStartTag(string $tag, ?array $attrs=null, bool $empty=false, bool $encode=true, bool $escape=true)
Writes a starttag.
getUserSpecificResults(array $finished_ids)
Calculates the evaluation data for the user specific results.
deleteWorkingData(int $question_id, int $active_id)
Deletes the working data of a question in the database.
ilLogger $log
setAnonymousUserList(bool $a_value)
$message
Definition: xapiexit.php:31
sendRaterNotification(int $a_user_id, int $a_appraisee_id)
send360ReminderToUser(int $a_user_id, array $a_appraisee_ids)
static _getInstance(int $a_copy_id)
xmlElement(string $tag, $attrs=null, $data=null, $encode=true, $escape=true)
Writes a basic element (no children, just textual content)
insertQuestion(int $question_id)
Inserts a question in the survey and saves the relation to the database.
getUserDataFromActiveId(int $active_id, bool $force_non_anonymous=false)
Returns run information.
closeAppraisee(int $a_user_id)
ilPluginAdmin $plugin_admin
ILIAS Survey InternalDataService $data_manager
const IL_CAL_TIMESTAMP
setSkillService(bool $a_val)
debug(string $message, array $context=[])
__construct(int $a_id=0, bool $a_call_by_reference=true)
const ANONYMIZE_CODE_ALL
const NOTIFICATION_RATERS
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
addMaterialTag(ilXmlWriter $a_xml_writer, string $a_material, bool $close_material_tag=true, bool $add_mobs=true, ?array $attribs=null)
Creates an XML material tag from a plain text or xhtml text.
setMode(int $a_value)
static prepareTextareaOutput(string $txt_output, bool $prepare_for_latex_output=false, bool $omitNl2BrWhenTextArea=false)
Prepares a string for a text area output where latex code may be in it If the text is HTML-free...
getRatersData(int $a_appraisee_id)
sendNotificationMail(int $a_user_id, string $a_anonymize_id, int $a_appr_id=0)
These mails are sent to tutors for each single participant that finishes a survey.
Basic class for all survey question types The SurveyQuestionGUI class defines and encapsulates basic ...
sendCodes(int $not_sent, string $subject, string $message, string $lang)
setReminderStatus(bool $a_value)
static getInstance(int $obj_id)
static _lookupEmail(int $a_user_id)
saveHeading(string $heading, int $insertbefore)
Class ilObjectActivation.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getUserSurveyExecutionStatus(?string $a_code=null)
Get current user execution status.
getFinishedIdForAppraiseeIdAndRaterId(int $a_appr_id, int $a_rat_id)
Get finished id for an appraisee and a rater.
deleteConstraint(int $constraint_id)
Deletes a single constraint.
createQuestionblock(string $title, bool $show_questiontext, bool $show_blocktitle, array $questions, bool $compress_view=false)
const RESULTS_SELF_EVAL_ALL
checkAccessOfUser(int $a_user_id, string $a_permission, string $a_cmd, int $a_ref_id, string $a_type="", ?int $a_obj_id=null, ?int $a_tree_id=null)
check access for an object (provide $a_type and $a_obj_id if available for better performance) ...
saveUserSettings(int $usr_id, string $key, string $title, string $value)
removeQuestion(int $question_id)
finishSurvey(int $finished_id, int $appr_id=0)
Finishes the survey creating an entry in the database.
sent360Reminders()
Send 360 reminders.
getMailAddresses()
(Tutor) Recipients of "single participant has finished" mails
static makeDir(string $a_dir)
creates a new directory and inherits all filesystem permissions of the parent directory You may pass ...
array $tutor_res_recipients
setReminderStart(?ilDate $a_value=null)
getLastActivePage(int $active_id)
Returns the question id of the last active page a user visited in a survey.
locateImportFiles(string $a_dir)
Locates the import directory and the xml file in a directory with an unzipped import file...
static sortArray(array $array, string $a_array_sortby_key, string $a_array_sortorder="asc", bool $a_numeric=false, bool $a_keep_keys=false)
setActivationEndDate(?int $ending_time=null)
static _lookupLogin(int $a_user_id)