ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
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;
117  protected ?ilDate $reminder_start = null;
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  "pool_usage" => array("integer", $this->getPoolUsage()),
729  // Mode type
730  "mode" => array("integer", $this->getMode()),
731  // 360°
732  "mode_360_self_eval" => array("integer", $this->get360SelfEvaluation()),
733  "mode_360_self_rate" => array("integer", $this->get360SelfRaters()),
734  "mode_360_self_appr" => array("integer", $this->get360SelfAppraisee()),
735  "mode_360_results" => array("integer", $this->get360Results()),
736  // competences
737  "mode_skill_service" => array("integer", (int) $this->getSkillService()),
738  // Self Evaluation Only
739  "mode_self_eval_results" => array("integer", self::RESULTS_SELF_EVAL_OWN),
740  // reminder/notification
741  "reminder_status" => array("integer", (int) $this->getReminderStatus()),
742  "reminder_start" => array("datetime", $rmd_start),
743  "reminder_end" => array("datetime", $rmd_end),
744  "reminder_frequency" => array("integer", $this->getReminderFrequency()),
745  "reminder_target" => array("integer", $this->getReminderTarget()),
746  "reminder_last_sent" => array("datetime", $this->getReminderLastSent()),
747  "reminder_tmpl" => array("text", $this->getReminderTemplate(true)),
748  "tutor_ntf_status" => array("integer", (int) $this->getTutorNotificationStatus()),
749  "tutor_ntf_reci" => array("text", implode(";", $this->getTutorNotificationRecipients())),
750  "tutor_ntf_target" => array("integer", $this->getTutorNotificationTarget()),
751  "own_results_view" => array("integer", $this->hasViewOwnResults()),
752  "own_results_mail" => array("integer", $this->hasMailOwnResults()),
753  "tutor_res_status" => array("integer", (int) $this->getTutorResultsStatus()),
754  "tutor_res_reci" => array("text", implode(";", $this->getTutorResultsRecipients())),
755  "confirmation_mail" => array("integer", $this->hasMailConfirmation()),
756  "anon_user_list" => array("integer", $this->hasAnonymousUserList()),
757  "calculate_sum_score" => array("integer", $this->getCalculateSumScore())
758  ));
759  $this->setSurveyId($next_id);
760  } else {
761  $affectedRows = $ilDB->update("svy_svy", array(
762  "author" => array("text", $this->getAuthor()),
763  "introduction" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getIntroduction(), 0)),
764  "outro" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getOutro(), 0)),
765  "startdate" => array("text", $this->getStartDate()),
766  "enddate" => array("text", $this->getEndDate()),
767  "evaluation_access" => array("text", $this->getEvaluationAccess()),
768  "complete" => array("text", $this->isComplete()),
769  "anonymize" => array("text", $this->getAnonymize()),
770  "show_question_titles" => array("text", $this->getShowQuestionTitles()),
771  "mailnotification" => array('integer', ($this->getMailNotification()) ? 1 : 0),
772  "mailaddresses" => array('text', $this->getMailAddresses()),
773  "mailparticipantdata" => array('text', $this->getMailParticipantData()),
774  "tstamp" => array("integer", time()),
775  "pool_usage" => array("integer", $this->getPoolUsage()),
776  //MODE TYPE
777  "mode" => array("integer", $this->getMode()),
778  // 360°
779  "mode_360_self_eval" => array("integer", $this->get360SelfEvaluation()),
780  "mode_360_self_rate" => array("integer", $this->get360SelfRaters()),
781  "mode_360_self_appr" => array("integer", $this->get360SelfAppraisee()),
782  "mode_360_results" => array("integer", $this->get360Results()),
783  // Competences
784  "mode_skill_service" => array("integer", (int) $this->getSkillService()),
785  // Self Evaluation Only
786  "mode_self_eval_results" => array("integer", $this->getSelfEvaluationResults()),
787  // reminder/notification
788  "reminder_status" => array("integer", $this->getReminderStatus()),
789  "reminder_start" => array("datetime", $rmd_start),
790  "reminder_end" => array("datetime", $rmd_end),
791  "reminder_frequency" => array("integer", $this->getReminderFrequency()),
792  "reminder_target" => array("integer", $this->getReminderTarget()),
793  "reminder_last_sent" => array("datetime", $this->getReminderLastSent()),
794  "reminder_tmpl" => array("text", $this->getReminderTemplate()),
795  "tutor_ntf_status" => array("integer", $this->getTutorNotificationStatus()),
796  "tutor_ntf_reci" => array("text", implode(";", $this->getTutorNotificationRecipients())),
797  "tutor_ntf_target" => array("integer", $this->getTutorNotificationTarget()),
798  "own_results_view" => array("integer", $this->hasViewOwnResults()),
799  "own_results_mail" => array("integer", $this->hasMailOwnResults()),
800  "tutor_res_status" => array("integer", (int) $this->getTutorResultsStatus()),
801  "tutor_res_reci" => array("text", implode(";", $this->getTutorResultsRecipients())),
802  "confirmation_mail" => array("integer", $this->hasMailConfirmation()),
803  "anon_user_list" => array("integer", $this->hasAnonymousUserList()),
804  "calculate_sum_score" => array("integer", $this->getCalculateSumScore())
805  ), array(
806  "survey_id" => array("integer", $this->getSurveyId())
807  ));
808  }
809  if ($affectedRows) {
810  // save questions to db
811  $this->saveQuestionsToDb();
812  }
813 
814  // moved activation to ilObjectActivation
815  if ($this->ref_id) {
816  ilObjectActivation::getItem($this->ref_id);
817 
818  $item = new ilObjectActivation();
819  if (!$this->isActivationLimited()) {
820  $item->setTimingType(ilObjectActivation::TIMINGS_DEACTIVATED);
821  } else {
822  $item->setTimingType(ilObjectActivation::TIMINGS_ACTIVATION);
823  $item->setTimingStart($this->getActivationStartDate());
824  $item->setTimingEnd($this->getActivationEndDate());
825  $item->toggleVisible($this->getActivationVisibility());
826  }
827 
828  $item->update($this->ref_id);
829  }
830  }
831 
832  // Saves the survey questions to db
833  public function saveQuestionsToDb(): void
834  {
835  $ilDB = $this->db;
836 
837  $this->svy_log->debug("save questions");
838 
839  // gather old questions state
840  $old_questions = array();
841  $result = $ilDB->queryF(
842  "SELECT survey_question_id,question_fi,sequence" .
843  " FROM svy_svy_qst WHERE survey_fi = %s",
844  array('integer'),
845  array($this->getSurveyId())
846  );
847  while ($row = $ilDB->fetchAssoc($result)) {
848  $old_questions[$row["question_fi"]] = $row; // problem, as soon as duplicates exist, they will be hidden here
849  }
850 
851  // #15231 - diff with current questions state
852  $insert = $update = $delete = array();
853  foreach ($this->questions as $seq => $fi) {
854  if (!array_key_exists($fi, $old_questions)) { // really new fi IDs
855  $insert[] = $fi; // this should be ok, should not create duplicates here
856  } elseif ($old_questions[$fi]["sequence"] != $seq) { // we are updating one of the duplicates (if any)
857  $update[$fi] = $old_questions[$fi]["survey_question_id"];
858  }
859  // keep track of still relevant questions
860  unset($old_questions[$fi]); // deleting old question, if they are not in current array
861  }
862 
863  // delete obsolete question relations
864  if (count($old_questions)) {
865  $del_ids = array();
866  foreach ($old_questions as $fi => $old) {
867  $del_ids[] = $old["survey_question_id"];
868  }
869  $ilDB->manipulate($q = "DELETE FROM svy_svy_qst" .
870  " WHERE " . $ilDB->in("survey_question_id", $del_ids, "", "integer"));
871  $this->svy_log->debug("delete: " . $q);
872  }
873  unset($old_questions);
874 
875  // create/update question relations
876  foreach ($this->questions as $seq => $fi) {
877  if (in_array($fi, $insert)) {
878  // check if question is not already in the survey, see #22018
879  if (!$this->isQuestionInSurvey($fi)) {
880  $next_id = $ilDB->nextId('svy_svy_qst');
881  $ilDB->manipulateF(
882  "INSERT INTO svy_svy_qst" .
883  " (survey_question_id, survey_fi, question_fi, heading, sequence, tstamp)" .
884  " VALUES (%s, %s, %s, %s, %s, %s)",
885  array('integer', 'integer', 'integer', 'text', 'integer', 'integer'),
886  array($next_id, $this->getSurveyId(), $fi, null, $seq, time())
887  );
888  $this->svy_log->debug("insert svy_svy_qst, id:" . $next_id . ", fi: " . $fi . ", seq:" . $seq);
889  }
890  } elseif (array_key_exists($fi, $update)) {
891  $ilDB->manipulate("UPDATE svy_svy_qst" .
892  " SET sequence = " . $ilDB->quote($seq, "integer") .
893  ", tstamp = " . $ilDB->quote(time(), "integer") .
894  " WHERE survey_question_id = " . $ilDB->quote($update[$fi], "integer"));
895  $this->svy_log->debug("update svy_svy_qst, id:" . $update[$fi] . ", fi: " . $fi . ", seq:" . $seq);
896  }
897  }
898  }
899 
900  // Returns a question gui object to a given questiontype and question id
901  public function getQuestionGUI(
902  string $questiontype,
903  int $question_id
904  ): SurveyQuestionGUI {
905  return SurveyQuestionGUI::_getQuestionGUI($questiontype, $question_id);
906  }
907 
908  // Returns the question type of a question with a given id
909  public function getQuestionType(
910  int $question_id
911  ): string {
912  $ilDB = $this->db;
913  if ($question_id < 1) {
914  return -1;
915  }
916  $result = $ilDB->queryF(
917  "SELECT type_tag FROM svy_question, svy_qtype WHERE svy_question.question_id = %s AND " .
918  "svy_question.questiontype_fi = svy_qtype.questiontype_id",
919  array('integer'),
920  array($question_id)
921  );
922  if ($result->numRows() === 1) {
923  $data = $ilDB->fetchAssoc($result);
924  return $data["type_tag"];
925  } else {
926  return "";
927  }
928  }
929 
930  public function getSurveyId(): int
931  {
932  return $this->survey_id;
933  }
934 
938  public function setAnonymize(int $a_anonymize): void
939  {
940  switch ($a_anonymize) {
941  case self::ANONYMIZE_OFF:
942  case self::ANONYMIZE_ON:
943  case self::ANONYMIZE_FREEACCESS:
944  case self::ANONYMIZE_CODE_ALL:
945  $this->anonymize = $a_anonymize;
946  break;
947  default:
948  $this->anonymize = self::ANONYMIZE_OFF;
949  break;
950  }
951  }
952 
953  public function getAnonymize(): int
954  {
955  return $this->anonymize;
956  }
957 
958  public function setCalculateSumScore(
959  bool $a_val
960  ): void {
961  $this->calculate_sum_score = $a_val;
962  }
963 
964  public function getCalculateSumScore(): bool
965  {
967  }
968 
969  // Checks if the survey is accessible without a survey code
970  public function isAccessibleWithoutCode(): bool
971  {
972  return ($this->getAnonymize() === self::ANONYMIZE_OFF ||
973  $this->getAnonymize() === self::ANONYMIZE_FREEACCESS);
974  }
975 
976  // Checks if the survey results are to be anonymized
977  public function hasAnonymizedResults(): bool
978  {
979  return ($this->getAnonymize() === self::ANONYMIZE_ON ||
980  $this->getAnonymize() === self::ANONYMIZE_FREEACCESS);
981  }
982 
983  public function loadFromDb(): void
984  {
985  $ilDB = $this->db;
986  $result = $ilDB->queryF(
987  "SELECT * FROM svy_svy WHERE obj_fi = %s",
988  array('integer'),
989  array($this->getId())
990  );
991  if ($result->numRows() === 1) {
992  $data = $ilDB->fetchAssoc($result);
993  $this->setSurveyId($data["survey_id"]);
994  $this->setAuthor($data["author"] ?? "");
995  $this->setIntroduction(ilRTE::_replaceMediaObjectImageSrc((string) $data["introduction"], 1));
996  if ($data["outro"] === "survey_finished") {
997  $this->setOutro($this->lng->txt("survey_finished"));
998  } else {
999  $this->setOutro(ilRTE::_replaceMediaObjectImageSrc((string) $data["outro"], 1));
1000  }
1001  $this->setShowQuestionTitles((bool) $data["show_question_titles"]);
1002  $this->setStartDate((string) ($data["startdate"] ?? ""));
1003  $this->setEndDate((string) ($data["enddate"] ?? ""));
1004  $this->setAnonymize((int) $data["anonymize"]);
1005  $this->setEvaluationAccess($data["evaluation_access"] ?? "");
1006  $this->loadQuestionsFromDb();
1007  $this->setMailNotification((bool) $data['mailnotification']);
1008  $this->setMailAddresses((string) $data['mailaddresses']);
1009  $this->setMailParticipantData((string) $data['mailparticipantdata']);
1010  $this->setPoolUsage((bool) $data['pool_usage']);
1011  // Mode
1012  $this->setMode($data['mode']);
1013  // 360°
1014  $this->set360SelfEvaluation((bool) $data['mode_360_self_eval']);
1015  $this->set360SelfRaters((bool) $data['mode_360_self_rate']);
1016  $this->set360SelfAppraisee((bool) $data['mode_360_self_appr']);
1017  $this->set360Results((int) $data['mode_360_results']);
1018  // Mode self evaluated
1019  $this->setSelfEvaluationResults((int) $data['mode_self_eval_results']);
1020  // Competences
1021  $this->setSkillService((bool) $data['mode_skill_service']);
1022  // reminder/notification
1023  $this->setReminderStatus((bool) $data["reminder_status"]);
1024  $this->setReminderStart($data["reminder_start"] ? new ilDate($data["reminder_start"], IL_CAL_DATE) : null);
1025  $this->setReminderEnd($data["reminder_end"] ? new ilDate($data["reminder_end"], IL_CAL_DATE) : null);
1026  $this->setReminderFrequency((int) $data["reminder_frequency"]);
1027  $this->setReminderTarget((int) $data["reminder_target"]);
1028  $this->setReminderLastSent((string) $data["reminder_last_sent"]);
1029  $this->setReminderTemplate((int) $data["reminder_tmpl"]);
1030  $this->setTutorNotificationStatus($data["tutor_ntf_status"]);
1031  $this->setTutorNotificationRecipients(explode(";", $data["tutor_ntf_reci"] ?? ""));
1032  $this->setTutorNotificationTarget($data["tutor_ntf_target"]);
1033  $this->setTutorResultsStatus((bool) $data["tutor_res_status"]);
1034  $this->setTutorResultsRecipients(explode(";", $data["tutor_res_reci"] ?? ""));
1035 
1036  $this->setViewOwnResults((bool) $data["own_results_view"]);
1037  $this->setMailOwnResults((bool) $data["own_results_mail"]);
1038  $this->setMailConfirmation((bool) $data["confirmation_mail"]);
1039  $this->setCalculateSumScore((bool) $data["calculate_sum_score"]);
1040 
1041  $this->setAnonymousUserList((bool) $data["anon_user_list"]);
1042  }
1043 
1044  // moved activation to ilObjectActivation
1045  if (isset($this->ref_id) && $this->ref_id !== 0) {
1046  $activation = ilObjectActivation::getItem($this->ref_id);
1047  switch ($activation["timing_type"]) {
1049  $this->setActivationLimited(true);
1050  $this->setActivationStartDate($activation["timing_start"]);
1051  $this->setActivationEndDate($activation["timing_end"]);
1052  $this->setActivationVisibility($activation["visible"]);
1053  break;
1054 
1055  default:
1056  $this->setActivationLimited(false);
1057  break;
1058  }
1059  }
1060  }
1061 
1062  public function loadQuestionsFromDb(): void
1063  {
1064  $ilDB = $this->db;
1065  $this->questions = array();
1066  $result = $ilDB->queryF(
1067  "SELECT * FROM svy_svy_qst WHERE survey_fi = %s ORDER BY sequence",
1068  array('integer'),
1069  array($this->getSurveyId())
1070  );
1071  while ($data = $ilDB->fetchAssoc($result)) {
1072  $this->questions[$data["sequence"]] = $data["question_fi"];
1073  }
1074  }
1075 
1076  // Remove duplicate sequence entries, see #22018
1077  public function fixSequenceStructure(): void
1078  {
1079  global $DIC;
1080 
1081  $ilDB = $DIC->database();
1082  //return;
1083  // we keep all survey question ids with their lowest sequence
1084  $result = $ilDB->queryF(
1085  "SELECT * FROM svy_svy_qst WHERE survey_fi = %s ORDER BY sequence",
1086  array('integer'),
1087  array($this->getSurveyId())
1088  );
1089 
1090  // step 1: find duplicates -> $to_delete_ids
1091  $fis = array();
1092  $to_delete_ids = array();
1093  while ($data = $ilDB->fetchAssoc($result)) {
1094  if (in_array($data["question_fi"], $fis)) { // found a duplicate
1095  $to_delete_ids[] = $data["survey_question_id"];
1096  } else {
1097  $fis[] = $data["question_fi"];
1098  }
1099  }
1100 
1101  // step 2: we delete the duplicates
1102  if (count($to_delete_ids) > 0) {
1103  $ilDB->manipulate($q = "DELETE FROM svy_svy_qst" .
1104  " WHERE " . $ilDB->in("survey_question_id", $to_delete_ids, false, "integer") .
1105  " AND survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer"));
1106  $this->svy_log->debug("delete: " . $q);
1107 
1108  $ilDB->manipulate($q = "DELETE FROM svy_qblk_qst " .
1109  " WHERE " . $ilDB->in("question_fi", $fis, true, "integer") .
1110  " AND survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer"));
1111  $this->svy_log->debug("delete: " . $q);
1112  }
1113 
1114  // step 3: we fix the sequence
1115  $set = $ilDB->query("SELECT * FROM svy_svy_qst " .
1116  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") . " ORDER BY sequence");
1117  $seq = 0;
1118  while ($rec = $ilDB->fetchAssoc($set)) {
1119  $ilDB->manipulate(
1120  $q = "UPDATE svy_svy_qst SET " .
1121  " sequence = " . $ilDB->quote($seq++, "integer") .
1122  " WHERE survey_question_id = " . $ilDB->quote($rec["survey_question_id"], "integer")
1123  );
1124  $this->svy_log->debug("update: " . $q);
1125  }
1126  }
1127 
1128  public function setAuthor(
1129  string $author = ""
1130  ): void {
1131  $this->author = $author;
1132  }
1133 
1139  public function saveAuthorToMetadata(
1140  string $a_author = ""
1141  ): void {
1142  $md = new ilMD($this->getId(), 0, $this->getType());
1143  $md_life = $md->getLifecycle();
1144  if (!$md_life) {
1145  if ($a_author === '') {
1146  $ilUser = $this->user;
1147  $a_author = $ilUser->getFullname();
1148  }
1149 
1150  $md_life = $md->addLifecycle();
1151  $md_life->save();
1152  $con = $md_life->addContribute();
1153  $con->setRole("Author");
1154  $con->save();
1155  $ent = $con->addEntity();
1156  $ent->setEntity($a_author);
1157  $ent->save();
1158  }
1159  }
1160 
1161  // Gets the authors name from metadata
1162  public function getAuthor(): string
1163  {
1164  $author = array();
1165  $md = new ilMD($this->getId(), 0, $this->getType());
1166  $md_life = $md->getLifecycle();
1167  if ($md_life) {
1168  $ids = $md_life->getContributeIds();
1169  foreach ($ids as $id) {
1170  $md_cont = $md_life->getContribute($id);
1171  if (strcmp($md_cont->getRole(), "Author") === 0) {
1172  $entids = $md_cont->getEntityIds();
1173  foreach ($entids as $entid) {
1174  $md_ent = $md_cont->getEntity($entid);
1175  $author[] = $md_ent->getEntity();
1176  }
1177  }
1178  }
1179  }
1180  return implode(",", $author);
1181  }
1182 
1183  public function getShowQuestionTitles(): bool
1184  {
1185  return (bool) $this->display_question_titles;
1186  }
1187 
1188  public function setShowQuestionTitles(bool $a_show): void
1189  {
1190  $this->display_question_titles = $a_show;
1191  }
1192 
1193  public function setIntroduction(
1194  string $introduction = ""
1195  ): void {
1196  $this->introduction = $introduction;
1197  }
1198 
1199  public function setOutro(
1200  string $outro = ""
1201  ): void {
1202  $this->outro = $outro;
1203  }
1204 
1205  public function getStartDate(): string
1206  {
1207  return $this->start_date;
1208  }
1209 
1213  public function setStartDate(
1214  string $start_date = ""
1215  ): void {
1216  $this->start_date = $start_date;
1217  }
1218 
1223  public function setStartDateAndTime(
1224  string $start_date,
1225  string $start_time
1226  ): void {
1227  $y = '';
1228  $m = '';
1229  $d = '';
1230  $h = '';
1231  $i = '';
1232  $s = '';
1233  if (preg_match("/(\d{4})-(\d{2})-(\d{2})/", $start_date, $matches)) {
1234  $y = $matches[1];
1235  $m = $matches[2];
1236  $d = $matches[3];
1237  }
1238  if (preg_match("/(\d{2}):(\d{2}):(\d{2})/", $start_time, $matches)) {
1239  $h = $matches[1];
1240  $i = $matches[2];
1241  $s = $matches[3];
1242  }
1243  $this->start_date = sprintf('%04d%02d%02d%02d%02d%02d', $y, $m, $d, $h, $i, $s);
1244  }
1245 
1246  public function getEndDate(): string
1247  {
1248  return $this->end_date;
1249  }
1250 
1254  public function setEndDate(
1255  string $end_date = ""
1256  ): void {
1257  $this->end_date = $end_date;
1258  }
1259 
1260  public function hasStarted(): bool
1261  {
1262  $start = $this->getStartDate();
1263  if ($start) {
1264  $start_date = new ilDateTime($start, IL_CAL_TIMESTAMP);
1265  return ($start_date->get(IL_CAL_UNIX) < time());
1266  }
1267  return true;
1268  }
1269 
1270  public function hasEnded(): bool
1271  {
1272  $end = $this->getEndDate();
1273  if ($end) {
1274  $end_date = new ilDateTime($end, IL_CAL_TIMESTAMP);
1275  return ($end_date->get(IL_CAL_UNIX) < time());
1276  }
1277  return false;
1278  }
1279 
1284  public function setEndDateAndTime(
1285  string $end_date,
1286  string $end_time
1287  ): void {
1288  $y = '';
1289  $m = '';
1290  $d = '';
1291  $h = '';
1292  $i = '';
1293  $s = '';
1294  if (preg_match("/(\d{4})-(\d{2})-(\d{2})/", $end_date, $matches)) {
1295  $y = $matches[1];
1296  $m = $matches[2];
1297  $d = $matches[3];
1298  }
1299  if (preg_match("/(\d{2}):(\d{2}):(\d{2})/", $end_time, $matches)) {
1300  $h = $matches[1];
1301  $i = $matches[2];
1302  $s = $matches[3];
1303  }
1304  $this->end_date = sprintf('%04d%02d%02d%02d%02d%02d', $y, $m, $d, $h, $i, $s);
1305  }
1306 
1307  // Gets the learners evaluation access
1308  public function getEvaluationAccess(): string
1309  {
1310  return $this->evaluation_access;
1311  }
1312 
1313  public function setEvaluationAccess(
1314  string $evaluation_access = self::EVALUATION_ACCESS_OFF
1315  ): void {
1316  $this->evaluation_access = $evaluation_access;
1317  }
1318 
1319  public function setActivationVisibility(
1320  bool $a_value
1321  ): void {
1322  $this->activation_visibility = $a_value;
1323  }
1324 
1325  public function getActivationVisibility(): bool
1326  {
1328  }
1329 
1330  public function isActivationLimited(): bool
1331  {
1333  }
1334 
1335  public function setActivationLimited(bool $a_value): void
1336  {
1337  $this->activation_limited = $a_value;
1338  }
1339 
1340  public function getIntroduction(): string
1341  {
1342  return $this->introduction;
1343  }
1344 
1345  public function getOutro(): string
1346  {
1347  return $this->outro;
1348  }
1349 
1354  public function getExistingQuestions(): array
1355  {
1356  $ilDB = $this->db;
1357  $existing_questions = array();
1358  $result = $ilDB->queryF(
1359  "SELECT svy_question.original_id FROM svy_question, svy_svy_qst WHERE " .
1360  "svy_svy_qst.survey_fi = %s AND svy_svy_qst.question_fi = svy_question.question_id",
1361  array('integer'),
1362  array($this->getSurveyId())
1363  );
1364  while ($data = $ilDB->fetchAssoc($result)) {
1365  if ($data["original_id"]) {
1366  $existing_questions[] = (int) $data["original_id"];
1367  }
1368  }
1369  return $existing_questions;
1370  }
1371 
1376  public function getQuestionpoolTitles(
1377  bool $could_be_offline = false,
1378  bool $showPath = false
1379  ): array {
1380  return ilObjSurveyQuestionPool::_getAvailableQuestionpools(true, $could_be_offline, $showPath);
1381  }
1382 
1390  public function moveQuestions(
1391  array $move_questions,
1392  int $target_index,
1393  int $insert_mode
1394  ): void {
1395  $array_pos = array_search($target_index, $this->questions);
1396  $part1 = $part2 = [];
1397  if ($insert_mode === 0) {
1398  $part1 = array_slice($this->questions, 0, $array_pos);
1399  $part2 = array_slice($this->questions, $array_pos);
1400  } elseif ($insert_mode === 1) {
1401  $part1 = array_slice($this->questions, 0, $array_pos + 1);
1402  $part2 = array_slice($this->questions, $array_pos + 1);
1403  }
1404  $found = 0;
1405  foreach ($move_questions as $question_id) {
1406  if (!(!in_array($question_id, $part1))) {
1407  unset($part1[array_search($question_id, $part1)]);
1408  $found++;
1409  }
1410  if (!(!in_array($question_id, $part2))) {
1411  unset($part2[array_search($question_id, $part2)]);
1412  $found++;
1413  }
1414  }
1415  // sanity check: do not move questions if they have not be found in the array
1416  if ($found !== count($move_questions)) {
1417  return;
1418  }
1419  $part1 = array_values($part1);
1420  $part2 = array_values($part2);
1421  $this->questions = array_values(array_merge($part1, $move_questions, $part2));
1422  foreach ($move_questions as $question_id) {
1423  $constraints = $this->getConstraints($question_id);
1424  foreach ($constraints as $idx => $constraint) {
1425  foreach ($part2 as $next_question_id) {
1426  if ($constraint["question"] == $next_question_id) {
1427  // constraint concerning a question that follows -> delete constraint
1428  $this->deleteConstraint($constraint["id"]);
1429  }
1430  }
1431  }
1432  }
1433  $this->saveQuestionsToDb();
1434  }
1435 
1439  public function removeQuestion(
1440  int $question_id
1441  ): void {
1442  $question = self::_instanciateQuestion($question_id);
1443  #20610 if no question found, do nothing.
1444  if ($question) {
1445  $question->delete($question_id);
1446  $this->removeConstraintsConcerningQuestion($question_id);
1447  }
1448  }
1449 
1455  int $question_id
1456  ): void {
1457  $ilDB = $this->db;
1458  $result = $ilDB->queryF(
1459  "SELECT constraint_fi FROM svy_qst_constraint WHERE question_fi = %s AND survey_fi = %s",
1460  array('integer','integer'),
1461  array($question_id, $this->getSurveyId())
1462  );
1463  if ($result->numRows() > 0) {
1464  $remove_constraints = array();
1465  while ($row = $ilDB->fetchAssoc($result)) {
1466  $remove_constraints[] = $row["constraint_fi"];
1467  }
1468  $affectedRows = $ilDB->manipulateF(
1469  "DELETE FROM svy_qst_constraint WHERE question_fi = %s AND survey_fi = %s",
1470  array('integer','integer'),
1471  array($question_id, $this->getSurveyId())
1472  );
1473  foreach ($remove_constraints as $key => $constraint_id) {
1474  $affectedRows = $ilDB->manipulateF(
1475  "DELETE FROM svy_constraint WHERE constraint_id = %s",
1476  array('integer'),
1477  array($constraint_id)
1478  );
1479  }
1480  }
1481  }
1482 
1488  public function removeQuestions(
1489  array $remove_questions,
1490  array $remove_questionblocks
1491  ): void {
1492  $ilDB = $this->db;
1493 
1494  $block_sizes = array();
1495  foreach ($this->getSurveyQuestions() as $question_id => $data) {
1496  if (in_array($question_id, $remove_questions) or in_array($data["questionblock_id"], $remove_questionblocks)) {
1497  unset($this->questions[array_search($question_id, $this->questions)]);
1498  $this->removeQuestion($question_id);
1499  } elseif ($data["questionblock_id"]) {
1500  $block_sizes[$data["questionblock_id"]] = ($block_sizes[$data["questionblock_id"]] ?? 0) + 1;
1501  }
1502  }
1503 
1504  // blocks with just 1 question need to be deleted
1505  foreach ($block_sizes as $block_id => $size) {
1506  if ($size < 2) {
1507  $remove_questionblocks[] = $block_id;
1508  }
1509  }
1510 
1511  foreach (array_unique($remove_questionblocks) as $questionblock_id) {
1512  $affectedRows = $ilDB->manipulateF(
1513  "DELETE FROM svy_qblk WHERE questionblock_id = %s",
1514  array('integer'),
1515  array($questionblock_id)
1516  );
1517  $affectedRows = $ilDB->manipulateF(
1518  "DELETE FROM svy_qblk_qst WHERE questionblock_fi = %s AND survey_fi = %s",
1519  array('integer','integer'),
1520  array($questionblock_id, $this->getSurveyId())
1521  );
1522  }
1523 
1524  $this->questions = array_values($this->questions);
1525  $this->saveQuestionsToDb();
1526  }
1527 
1532  public function unfoldQuestionblocks(
1533  array $questionblocks
1534  ): void {
1535  $ilDB = $this->db;
1536  foreach ($questionblocks as $index) {
1537  $affectedRows = $ilDB->manipulateF(
1538  "DELETE FROM svy_qblk WHERE questionblock_id = %s",
1539  array('integer'),
1540  array($index)
1541  );
1542  $affectedRows = $ilDB->manipulateF(
1543  "DELETE FROM svy_qblk_qst WHERE questionblock_fi = %s AND survey_fi = %s",
1544  array('integer','integer'),
1545  array($index, $this->getSurveyId())
1546  );
1547  }
1548  }
1549 
1553  public function removeQuestionFromBlock(
1554  int $question_id,
1555  int $questionblock_id
1556  ): void {
1557  $ilDB = $this->db;
1558 
1559  $ilDB->manipulateF(
1560  "DELETE FROM svy_qblk_qst WHERE questionblock_fi = %s AND survey_fi = %s AND question_fi = %s",
1561  array('integer','integer','integer'),
1562  array($questionblock_id, $this->getSurveyId(), $question_id)
1563  );
1564  }
1565 
1569  public function addQuestionToBlock(
1570  int $question_id,
1571  int $questionblock_id
1572  ): void {
1573  $ilDB = $this->db;
1574 
1575  // see #22018
1576  if (!$this->isQuestionInAnyBlock($question_id)) {
1577  $next_id = $ilDB->nextId('svy_qblk_qst');
1578  $affectedRows = $ilDB->manipulateF(
1579  "INSERT INTO svy_qblk_qst (qblk_qst_id, survey_fi, questionblock_fi, " .
1580  "question_fi) VALUES (%s, %s, %s, %s)",
1581  array('integer', 'integer', 'integer', 'integer'),
1582  array($next_id, $this->getSurveyId(), $questionblock_id, $question_id)
1583  );
1584 
1585  $this->deleteConstraints($question_id); // #13713
1586  }
1587  }
1588 
1592  public function isQuestionInAnyBlock(
1593  int $a_question_fi
1594  ): bool {
1595  global $DIC;
1596 
1597  $ilDB = $DIC->database();
1598 
1599  $set = $ilDB->query("SELECT * FROM svy_qblk_qst " .
1600  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
1601  " AND question_fi = " . $ilDB->quote($a_question_fi, "integer"));
1602  if ($rec = $ilDB->fetchAssoc($set)) {
1603  return true;
1604  }
1605  return false;
1606  }
1607 
1608 
1614  public function getQuestionblockQuestions(
1615  int $questionblock_id
1616  ): array {
1617  $ilDB = $this->db;
1618  $titles = array();
1619  $result = $ilDB->queryF(
1620  "SELECT svy_question.title, svy_qblk_qst.question_fi, svy_qblk_qst.survey_fi FROM " .
1621  "svy_qblk, svy_qblk_qst, svy_question WHERE svy_qblk.questionblock_id = svy_qblk_qst.questionblock_fi AND " .
1622  "svy_question.question_id = svy_qblk_qst.question_fi AND svy_qblk.questionblock_id = %s",
1623  array('integer'),
1624  array($questionblock_id)
1625  );
1626  $survey_id = "";
1627  while ($row = $ilDB->fetchAssoc($result)) {
1628  $titles[$row["question_fi"]] = $row["title"];
1629  $survey_id = $row["survey_fi"];
1630  }
1631  $result = $ilDB->queryF(
1632  "SELECT question_fi, sequence FROM svy_svy_qst WHERE survey_fi = %s ORDER BY sequence",
1633  array('integer'),
1634  array($survey_id)
1635  );
1636  $resultarray = array();
1637  $counter = 1;
1638  while ($row = $ilDB->fetchAssoc($result)) {
1639  if (array_key_exists($row["question_fi"], $titles)) {
1640  $resultarray[$counter++] = $titles[$row["question_fi"]];
1641  }
1642  }
1643  return $resultarray;
1644  }
1645 
1652  int $questionblock_id
1653  ): array {
1654  $ilDB = $this->db;
1655 
1656  // we need a correct order here, see #22011
1657  $result = $ilDB->queryF(
1658  "SELECT a.question_fi FROM svy_qblk_qst a JOIN svy_svy_qst b ON (a.question_fi = b.question_fi) " .
1659  " WHERE a.questionblock_fi = %s ORDER BY b.sequence",
1660  array("integer"),
1661  array($questionblock_id)
1662  );
1663  $ids = array();
1664  if ($result->numRows()) {
1665  while ($data = $ilDB->fetchAssoc($result)) {
1666  if (!in_array($data['question_fi'], $ids)) { // no duplicates, see #22018
1667  $ids[] = (int) $data['question_fi'];
1668  }
1669  }
1670  }
1671  return $ids;
1672  }
1673 
1678  public static function _getQuestionblock(
1679  int $questionblock_id
1680  ): array {
1681  global $DIC;
1682 
1683  $ilDB = $DIC->database();
1684  $result = $ilDB->queryF(
1685  "SELECT * FROM svy_qblk WHERE questionblock_id = %s",
1686  array('integer'),
1687  array($questionblock_id)
1688  );
1689  $row = $ilDB->fetchAssoc($result);
1690  return $row;
1691  }
1692 
1696  public static function _addQuestionblock(
1697  string $title = "",
1698  int $owner = 0,
1699  bool $show_questiontext = true,
1700  bool $show_blocktitle = false,
1701  bool $compress_view = false
1702  ): int {
1703  global $DIC;
1704 
1705  $ilDB = $DIC->database();
1706  $next_id = $ilDB->nextId('svy_qblk');
1707  $ilDB->manipulateF(
1708  "INSERT INTO svy_qblk (questionblock_id, title, show_questiontext," .
1709  " show_blocktitle, owner_fi, tstamp, compress_view) " .
1710  "VALUES (%s, %s, %s, %s, %s, %s, %s)",
1711  array('integer','text','integer','integer','integer','integer','integer'),
1712  array($next_id, $title, $show_questiontext, $show_blocktitle, $owner, time(),$compress_view)
1713  );
1714  return $next_id;
1715  }
1716 
1720  public function createQuestionblock(
1721  string $title,
1722  bool $show_questiontext,
1723  bool $show_blocktitle,
1724  array $questions,
1725  bool $compress_view = false
1726  ): void {
1727  $ilDB = $this->db;
1728 
1729  // if the selected questions are not in a continous selection, move all questions of the
1730  // questionblock at the position of the first selected question
1731  $this->moveQuestions($questions, $questions[0], 0);
1732 
1733  // now save the question block
1734  $ilUser = $this->user;
1735  $next_id = $ilDB->nextId('svy_qblk');
1736  $affectedRows = $ilDB->manipulateF(
1737  "INSERT INTO svy_qblk (questionblock_id, title, show_questiontext," .
1738  " show_blocktitle, owner_fi, tstamp, compress_view) VALUES (%s, %s, %s, %s, %s, %s, %s)",
1739  array('integer','text','text','text','integer','integer','integer'),
1740  array($next_id, $title, $show_questiontext, $show_blocktitle, $ilUser->getId(), time(), $compress_view)
1741  );
1742  if ($affectedRows) {
1743  $questionblock_id = $next_id;
1744  foreach ($questions as $index) {
1745  if (!$this->isQuestionInAnyBlock($index)) {
1746  $next_id = $ilDB->nextId('svy_qblk_qst'); // #22018
1747  $affectedRows = $ilDB->manipulateF(
1748  "INSERT INTO svy_qblk_qst (qblk_qst_id, survey_fi, questionblock_fi, " .
1749  "question_fi) VALUES (%s, %s, %s, %s)",
1750  array('integer', 'integer', 'integer', 'integer'),
1751  array($next_id, $this->getSurveyId(), $questionblock_id, $index)
1752  );
1753  $this->deleteConstraints($index);
1754  }
1755  }
1756  }
1757  }
1758 
1762  public function modifyQuestionblock(
1763  int $questionblock_id,
1764  string $title,
1765  bool $show_questiontext,
1766  bool $show_blocktitle,
1767  bool $compress_view = false
1768  ): void {
1769  $ilDB = $this->db;
1770  $ilDB->manipulateF(
1771  "UPDATE svy_qblk SET title = %s, show_questiontext = %s," .
1772  " show_blocktitle = %s, compress_view = %s WHERE questionblock_id = %s",
1773  array('text','text','text','integer', 'integer'),
1774  array($title, $show_questiontext, $show_blocktitle, $compress_view, $questionblock_id)
1775  );
1776  }
1777 
1782  public function deleteConstraints(
1783  int $question_id
1784  ): void {
1785  $ilDB = $this->db;
1786  $result = $ilDB->queryF(
1787  "SELECT constraint_fi FROM svy_qst_constraint WHERE question_fi = %s AND survey_fi = %s",
1788  array('integer','integer'),
1789  array($question_id, $this->getSurveyId())
1790  );
1791  $constraints = array();
1792  while ($row = $ilDB->fetchAssoc($result)) {
1793  $constraints[] = $row["constraint_fi"];
1794  }
1795  foreach ($constraints as $constraint_id) {
1796  $this->deleteConstraint($constraint_id);
1797  }
1798  }
1799 
1804  public function deleteConstraint(
1805  int $constraint_id
1806  ): void {
1807  $ilDB = $this->db;
1808  $affectedRows = $ilDB->manipulateF(
1809  "DELETE FROM svy_constraint WHERE constraint_id = %s",
1810  array('integer'),
1811  array($constraint_id)
1812  );
1813  $affectedRows = $ilDB->manipulateF(
1814  "DELETE FROM svy_qst_constraint WHERE constraint_fi = %s",
1815  array('integer'),
1816  array($constraint_id)
1817  );
1818  }
1819 
1824  public function getSurveyQuestions(
1825  bool $with_answers = false
1826  ): array {
1827  $ilDB = $this->db;
1828  // get questionblocks
1829  $all_questions = array();
1830  $result = $ilDB->queryF(
1831  "SELECT svy_qtype.type_tag, svy_qtype.plugin, svy_question.question_id, " .
1832  "svy_svy_qst.heading FROM svy_qtype, svy_question, svy_svy_qst WHERE svy_svy_qst.survey_fi = %s AND " .
1833  "svy_svy_qst.question_fi = svy_question.question_id AND svy_question.questiontype_fi = svy_qtype.questiontype_id " .
1834  "ORDER BY svy_svy_qst.sequence",
1835  array('integer'),
1836  array($this->getSurveyId())
1837  );
1838  while ($row = $ilDB->fetchAssoc($result)) {
1839  $add = true;
1840  if ($row["plugin"]) {
1841  $add = false;
1842  }
1843  if ($add) {
1844  $question = self::_instanciateQuestion($row["question_id"]);
1845  $questionrow = $question->getQuestionDataArray($row["question_id"]);
1846  foreach ($row as $key => $value) {
1847  $questionrow[$key] = $value;
1848  }
1849  $all_questions[$row["question_id"]] = $questionrow;
1850  $all_questions[$row["question_id"]]["usableForPrecondition"] = $question->usableForPrecondition();
1851  $all_questions[$row["question_id"]]["availableRelations"] = $question->getAvailableRelations();
1852  }
1853  }
1854  // get all questionblocks
1855  $questionblocks = array();
1856  if (count($all_questions)) {
1857  $result = $ilDB->queryF(
1858  "SELECT svy_qblk.*, svy_qblk_qst.question_fi FROM svy_qblk, svy_qblk_qst WHERE " .
1859  "svy_qblk.questionblock_id = svy_qblk_qst.questionblock_fi AND svy_qblk_qst.survey_fi = %s " .
1860  "AND " . $ilDB->in('svy_qblk_qst.question_fi', array_keys($all_questions), false, 'integer'),
1861  array('integer'),
1862  array($this->getSurveyId())
1863  );
1864  while ($row = $ilDB->fetchAssoc($result)) {
1865  $questionblocks[$row['question_fi']] = $row;
1866  }
1867  }
1868 
1869  foreach ($all_questions as $question_id => $row) {
1870  $constraints = $this->getConstraints($question_id);
1871  if (isset($questionblocks[$question_id])) {
1872  $all_questions[$question_id]["questionblock_title"] = $questionblocks[$question_id]['title'];
1873  $all_questions[$question_id]["questionblock_id"] = $questionblocks[$question_id]['questionblock_id'];
1874  } else {
1875  $all_questions[$question_id]["questionblock_title"] = "";
1876  $all_questions[$question_id]["questionblock_id"] = "";
1877  }
1878  $all_questions[$question_id]["constraints"] = $constraints;
1879  if ($with_answers) {
1880  $answers = array();
1881  $result = $ilDB->queryF(
1882  "SELECT svy_variable.*, svy_category.title FROM svy_variable, svy_category " .
1883  "WHERE svy_variable.question_fi = %s AND svy_variable.category_fi = svy_category.category_id " .
1884  "ORDER BY sequence ASC",
1885  array('integer'),
1886  array($question_id)
1887  );
1888  if ($result->numRows() > 0) {
1889  while ($data = $ilDB->fetchAssoc($result)) {
1890  $answers[] = $data["title"];
1891  }
1892  }
1893  $all_questions[$question_id]["answers"] = $answers;
1894  }
1895  }
1896  return $all_questions;
1897  }
1898 
1904  public function setObligatoryStates(
1905  array $obligatory_questions
1906  ): void {
1907  $ilDB = $this->db;
1908  $result = $ilDB->queryF(
1909  "SELECT * FROM svy_svy_qst WHERE survey_fi = %s",
1910  array('integer'),
1911  array($this->getSurveyId())
1912  );
1913  if ($result->numRows()) {
1914  while ($row = $ilDB->fetchAssoc($result)) {
1915  if (!array_key_exists($row["question_fi"], $obligatory_questions)) {
1916  $obligatory_questions[$row["question_fi"]] = 0;
1917  }
1918  }
1919  }
1920  // set the obligatory states in the database
1921  foreach ($obligatory_questions as $question_fi => $obligatory) {
1922  // #12420
1923  $ilDB->manipulate("UPDATE svy_question" .
1924  " SET obligatory = " . $ilDB->quote($obligatory, "integer") .
1925  " WHERE question_id = " . $ilDB->quote($question_fi, "integer"));
1926  }
1927  }
1928 
1933  public function getSurveyPages(): array
1934  {
1935  $ilDB = $this->db;
1936  // get questionblocks
1937  $all_questions = array();
1938  $result = $ilDB->queryF(
1939  "SELECT svy_question.*, svy_qtype.type_tag, svy_svy_qst.heading FROM " .
1940  "svy_question, svy_qtype, svy_svy_qst WHERE svy_svy_qst.survey_fi = %s AND " .
1941  "svy_svy_qst.question_fi = svy_question.question_id AND svy_question.questiontype_fi = svy_qtype.questiontype_id " .
1942  "ORDER BY svy_svy_qst.sequence",
1943  array('integer'),
1944  array($this->getSurveyId())
1945  );
1946  while ($row = $ilDB->fetchAssoc($result)) {
1947  $all_questions[$row["question_id"]] = $row;
1948  }
1949  // get all questionblocks
1950  $questionblocks = array();
1951  if (count($all_questions)) {
1952  $result = $ilDB->queryF(
1953  "SELECT svy_qblk.*, svy_qblk_qst.question_fi FROM svy_qblk, svy_qblk_qst " .
1954  "WHERE svy_qblk.questionblock_id = svy_qblk_qst.questionblock_fi AND svy_qblk_qst.survey_fi = %s " .
1955  "AND " . $ilDB->in('svy_qblk_qst.question_fi', array_keys($all_questions), false, 'integer'),
1956  array('integer'),
1957  array($this->getSurveyId())
1958  );
1959  while ($row = $ilDB->fetchAssoc($result)) {
1960  $questionblocks[$row['question_fi']] = $row;
1961  }
1962  }
1963 
1964  $all_pages = array();
1965  $pageindex = -1;
1966  $currentblock = "";
1967  foreach ($all_questions as $question_id => $row) {
1968  $constraints = array();
1969  if (isset($questionblocks[$question_id])) {
1970  if (!$currentblock or ($currentblock != $questionblocks[$question_id]['questionblock_id'])) {
1971  $pageindex++;
1972  }
1973  $all_questions[$question_id]['page'] = $pageindex;
1974  $all_questions[$question_id]["questionblock_title"] = $questionblocks[$question_id]['title'];
1975  $all_questions[$question_id]["questionblock_id"] = $questionblocks[$question_id]['questionblock_id'];
1976  $all_questions[$question_id]["questionblock_show_questiontext"] = $questionblocks[$question_id]['show_questiontext'];
1977  $all_questions[$question_id]["questionblock_show_blocktitle"] = $questionblocks[$question_id]['show_blocktitle'];
1978  $all_questions[$question_id]["questionblock_compress_view"] = $questionblocks[$question_id]['compress_view'];
1979  $currentblock = $questionblocks[$question_id]['questionblock_id'];
1980  } else {
1981  $pageindex++;
1982  $all_questions[$question_id]['page'] = $pageindex;
1983  $all_questions[$question_id]["questionblock_title"] = "";
1984  $all_questions[$question_id]["questionblock_id"] = "";
1985  $all_questions[$question_id]["questionblock_show_questiontext"] = 1;
1986  $all_questions[$question_id]["questionblock_show_blocktitle"] = 1;
1987  $all_questions[$question_id]["questionblock_compress_view"] = false;
1988  $currentblock = "";
1989  }
1990  $constraints = $this->getConstraints($question_id);
1991  $all_questions[$question_id]["constraints"] = $constraints;
1992  if (!isset($all_pages[$pageindex])) {
1993  $all_pages[$pageindex] = array();
1994  }
1995  $all_pages[$pageindex][] = $all_questions[$question_id];
1996  }
1997  // calculate position percentage for every page
1998  $max = count($all_pages);
1999  $counter = 1;
2000  foreach ($all_pages as $index => $block) {
2001  foreach ($block as $blockindex => $question) {
2002  $all_pages[$index][$blockindex]["position"] = $counter / $max;
2003  }
2004  $counter++;
2005  }
2006 
2007  return $all_pages;
2008  }
2009 
2018  public function getNextPage(
2019  int $active_page_question_id,
2020  int $direction
2021  ): ?array {
2022  $foundpage = -1;
2023  $pages = $this->getSurveyPages();
2024  if ($active_page_question_id === 0) {
2025  return $pages[0];
2026  }
2027  foreach ($pages as $key => $question_array) {
2028  foreach ($question_array as $question) {
2029  if ($active_page_question_id == $question["question_id"]) {
2030  $foundpage = $key;
2031  }
2032  }
2033  }
2034  if ($foundpage === -1) {
2035  throw new ilSurveyException("nextPage: Current page not found.");
2036  } else {
2037  $foundpage += $direction;
2038  if ($foundpage < 0) {
2039  return null;
2040  }
2041  if ($foundpage >= count($pages)) {
2042  return null;
2043  }
2044  return $pages[$foundpage];
2045  }
2046  }
2047 
2051  public function getAvailableQuestionpools(
2052  bool $use_obj_id = false,
2053  bool $could_be_offline = false,
2054  bool $showPath = false,
2055  string $permission = "read"
2056  ): array {
2057  return ilObjSurveyQuestionPool::_getAvailableQuestionpools($use_obj_id, $could_be_offline, $showPath, $permission);
2058  }
2059 
2064  public function getPrecondition(
2065  int $constraint_id
2066  ): array {
2067  $ilDB = $this->db;
2068 
2069  $result = $ilDB->queryF(
2070  "SELECT svy_constraint.*, svy_relation.*, svy_qst_constraint.question_fi ref_question_fi FROM svy_qst_constraint, svy_constraint, " .
2071  "svy_relation WHERE svy_constraint.relation_fi = svy_relation.relation_id AND " .
2072  "svy_qst_constraint.constraint_fi = svy_constraint.constraint_id AND svy_constraint.constraint_id = %s",
2073  array('integer'),
2074  array($constraint_id)
2075  );
2076  $pc = array();
2077  if ($result->numRows()) {
2078  $pc = $ilDB->fetchAssoc($result);
2079  }
2080  return $pc;
2081  }
2082 
2087  public function getConstraints(
2088  int $question_id
2089  ): array {
2090  $ilDB = $this->db;
2091 
2092  $result_array = array();
2093  $result = $ilDB->queryF(
2094  "SELECT svy_constraint.*, svy_relation.* FROM svy_qst_constraint, svy_constraint, svy_relation " .
2095  "WHERE svy_constraint.relation_fi = svy_relation.relation_id AND " .
2096  "svy_qst_constraint.constraint_fi = svy_constraint.constraint_id AND svy_qst_constraint.question_fi = %s " .
2097  "AND svy_qst_constraint.survey_fi = %s",
2098  array('integer','integer'),
2099  array($question_id, $this->getSurveyId())
2100  );
2101  while ($row = $ilDB->fetchAssoc($result)) {
2102  $question_type = SurveyQuestion::_getQuestionType($row["question_fi"]);
2103  SurveyQuestion::_includeClass($question_type);
2104  $question = new $question_type();
2105  $question->loadFromDb($row["question_fi"]);
2106  $valueoutput = $question->getPreconditionValueOutput($row["value"]);
2107  $result_array[] = array("id" => $row["constraint_id"],
2108  "question" => $row["question_fi"],
2109  "short" => $row["shortname"],
2110  "long" => $row["longname"],
2111  "value" => $row["value"],
2112  "conjunction" => $row["conjunction"],
2113  "valueoutput" => $valueoutput
2114  );
2115  }
2116  return $result_array;
2117  }
2118 
2122  public static function _getConstraints(
2123  int $survey_id
2124  ): array {
2125  global $DIC;
2126 
2127  $ilDB = $DIC->database();
2128  $result_array = array();
2129  $result = $ilDB->queryF(
2130  "SELECT svy_qst_constraint.question_fi as for_question, svy_constraint.*, svy_relation.* " .
2131  "FROM svy_qst_constraint, svy_constraint, svy_relation WHERE svy_constraint.relation_fi = svy_relation.relation_id " .
2132  "AND svy_qst_constraint.constraint_fi = svy_constraint.constraint_id AND svy_qst_constraint.survey_fi = %s",
2133  array('integer'),
2134  array($survey_id)
2135  );
2136  while ($row = $ilDB->fetchAssoc($result)) {
2137  $result_array[] = array("id" => $row["constraint_id"],
2138  "for_question" => $row["for_question"],
2139  "question" => $row["question_fi"],
2140  "short" => $row["shortname"],
2141  "long" => $row["longname"],
2142  "relation_id" => $row["relation_id"],
2143  "value" => $row["value"],
2144  'conjunction' => $row['conjunction']
2145  );
2146  }
2147  return $result_array;
2148  }
2149 
2150 
2155  public function getVariables(
2156  int $question_id
2157  ): array {
2158  $ilDB = $this->db;
2159 
2160  $result_array = array();
2161  $result = $ilDB->queryF(
2162  "SELECT svy_variable.*, svy_category.title FROM svy_variable LEFT JOIN " .
2163  "svy_category ON svy_variable.category_fi = svy_category.category_id WHERE svy_variable.question_fi = %s " .
2164  "ORDER BY svy_variable.sequence",
2165  array('integer'),
2166  array($question_id)
2167  );
2168  while ($row = $ilDB->fetchObject($result)) {
2169  $result_array[$row->sequence] = $row;
2170  }
2171  return $result_array;
2172  }
2173 
2183  public function addConstraint(
2184  int $if_question_id,
2185  int $relation,
2186  float $value,
2187  int $conjunction
2188  ): ?int {
2189  $ilDB = $this->db;
2190 
2191  $next_id = $ilDB->nextId('svy_constraint');
2192  $affectedRows = $ilDB->manipulateF(
2193  "INSERT INTO svy_constraint (constraint_id, question_fi, relation_fi, value, conjunction) VALUES " .
2194  "(%s, %s, %s, %s, %s)",
2195  array('integer','integer','integer','float', 'integer'),
2196  array($next_id, $if_question_id, $relation, $value, $conjunction)
2197  );
2198  if ($affectedRows) {
2199  return $next_id;
2200  } else {
2201  return null;
2202  }
2203  }
2204 
2205 
2209  public function addConstraintToQuestion(
2210  int $to_question_id,
2211  int $constraint_id
2212  ): void {
2213  $ilDB = $this->db;
2214 
2215  $next_id = $ilDB->nextId('svy_qst_constraint');
2216  $ilDB->manipulateF(
2217  "INSERT INTO svy_qst_constraint (question_constraint_id, survey_fi, question_fi, " .
2218  "constraint_fi) VALUES (%s, %s, %s, %s)",
2219  array('integer','integer','integer','integer'),
2220  array($next_id, $this->getSurveyId(), $to_question_id, $constraint_id)
2221  );
2222  }
2223 
2227  public function updateConstraint(
2228  int $precondition_id,
2229  int $if_question_id,
2230  int $relation,
2231  float $value,
2232  int $conjunction
2233  ): void {
2234  $ilDB = $this->db;
2235  $ilDB->manipulateF(
2236  "UPDATE svy_constraint SET question_fi = %s, relation_fi = %s, value = %s, conjunction = %s " .
2237  "WHERE constraint_id = %s",
2238  array('integer','integer','float','integer','integer'),
2239  array($if_question_id, $relation, $value, $conjunction, $precondition_id)
2240  );
2241  }
2242 
2247  array $questions,
2248  int $conjunction
2249  ): void {
2250  $ilDB = $this->db;
2251  foreach ($questions as $question_id) {
2252  $ilDB->manipulateF(
2253  "UPDATE svy_constraint SET conjunction = %s " .
2254  "WHERE constraint_id IN (SELECT constraint_fi FROM svy_qst_constraint WHERE svy_qst_constraint.question_fi = %s)",
2255  array('integer','integer'),
2256  array($conjunction, $question_id)
2257  );
2258  }
2259  }
2260 
2264  public function getAllRelations(
2265  bool $short_as_key = false
2266  ): array {
2267  $ilDB = $this->db;
2268 
2269  // #7987
2270  $custom_order = array("equal", "not_equal", "less", "less_or_equal", "more", "more_or_equal");
2271  $custom_order = array_flip($custom_order);
2272 
2273  $result_array = array();
2274  $result = $ilDB->query("SELECT * FROM svy_relation");
2275  while ($row = $ilDB->fetchAssoc($result)) {
2276  if ($short_as_key) {
2277  $result_array[$row["shortname"]] = array("short" => $row["shortname"], "long" => $row["longname"], "id" => $row["relation_id"], "order" => $custom_order[$row["longname"]]);
2278  } else {
2279  $result_array[$row["relation_id"]] = array("short" => $row["shortname"], "long" => $row["longname"], "order" => $custom_order[$row["longname"]]);
2280  }
2281  }
2282 
2283  $result_array = ilArrayUtil::sortArray($result_array, "order", "ASC", true, true);
2284  foreach ($result_array as $idx => $item) {
2285  unset($result_array[$idx]["order"]);
2286  }
2287 
2288  return $result_array;
2289  }
2290 
2291 
2292 
2293 
2298  public function deleteWorkingData(
2299  int $question_id,
2300  int $active_id
2301  ): void {
2302  $ilDB = $this->db;
2303 
2304  $affectedRows = $ilDB->manipulateF(
2305  "DELETE FROM svy_answer WHERE question_fi = %s AND active_fi = %s",
2306  array('integer','integer'),
2307  array($question_id, $active_id)
2308  );
2309  }
2310 
2315  public function loadWorkingData(
2316  int $question_id,
2317  int $active_id
2318  ): array {
2319  $ilDB = $this->db;
2320  $result_array = array();
2321  $result = $ilDB->queryF(
2322  "SELECT * FROM svy_answer WHERE question_fi = %s AND active_fi = %s",
2323  array('integer','integer'),
2324  array($question_id, $active_id)
2325  );
2326  if ($result->numRows() >= 1) {
2327  while ($row = $ilDB->fetchAssoc($result)) {
2328  $result_array[] = $row;
2329  }
2330  }
2331  return $result_array;
2332  }
2333 
2338  public function finishSurvey(
2339  int $finished_id,
2340  int $appr_id = 0
2341  ): void {
2342  $ilDB = $this->db;
2343 
2344  $ilDB->manipulateF(
2345  "UPDATE svy_finished SET state = %s, tstamp = %s" .
2346  " WHERE survey_fi = %s AND finished_id = %s",
2347  array('text','integer','integer','integer'),
2348  array(1, time(), $this->getSurveyId(), $finished_id)
2349  );
2350 
2351  // self eval writes skills on finishing
2352  if ($this->getMode() === self::MODE_SELF_EVAL) {
2353  $user = $this->getUserDataFromActiveId($finished_id);
2354  $sskill = new ilSurveySkill($this);
2355  $sskill->writeAndAddSelfEvalSkills($user['usr_id']);
2356  }
2357 
2358  // self eval writes skills on finishing
2359  if ($this->getMode() === self::MODE_IND_FEEDB) {
2360  // we use a rater id like "a27" for anonymous or
2361  // "123" for non anonymous user
2362  // @todo: move this e.g. to participant manager
2363  $raters = $this->getRatersData($appr_id);
2364  $run_manager = $this->survey_service
2365  ->domain()
2366  ->execution()
2367  ->run($this, $this->user->getId(), $appr_id);
2368  $run = $run_manager->getById($finished_id);
2369  $rater_id = "";
2370  if ($run->getUserId() !== 0 && $run->getUserId() !== ANONYMOUS_USER_ID) {
2371  $rater_id = $run->getUserId();
2372  } else {
2373  foreach ($raters as $id => $rater) {
2374  if (($rater["code"] ?? "") === $run->getCode()) {
2375  $rater_id = $id;
2376  }
2377  }
2378  }
2379  $sskill = new ilSurveySkill($this);
2380  //$sskill->writeAndAddSelfEvalSkills($user['usr_id']);
2381  $sskill->writeAndAddIndFeedbackSkills($finished_id, $appr_id, $rater_id);
2382  }
2383 
2384  $this->checkTutorNotification();
2385  }
2386 
2393  public function setPage(
2394  int $finished_id,
2395  int $page_id
2396  ): void {
2397  $ilDB = $this->db;
2398 
2399  $ilDB->manipulateF(
2400  "UPDATE svy_finished SET lastpage = %s WHERE finished_id = %s",
2401  array('integer','integer'),
2402  array(($page_id) ?: 0, $finished_id)
2403  );
2404  }
2405 
2412  public function sendNotificationMail(
2413  int $a_user_id,
2414  string $a_anonymize_id,
2415  int $a_appr_id = 0
2416  ): void {
2417  // #12755
2418  $placeholders = array(
2419  "FIRST_NAME" => "firstname",
2420  "LAST_NAME" => "lastname",
2421  "LOGIN" => "login",
2422  // old style
2423  "firstname" => "firstname"
2424  );
2425 
2426  //mailaddresses is just text split by commas.
2427  //sendMail can send emails if it gets an user id or an email as first parameter.
2428  $recipients = explode(",", $this->mailaddresses ?? "");
2429  foreach ($recipients as $recipient) {
2430  // #11298
2431  $ntf = new ilSystemNotification();
2432  $ntf->setLangModules(array("survey"));
2433  $ntf->setRefId($this->getRefId());
2434  $ntf->setSubjectLangId('finished_mail_subject');
2435 
2436  $messagetext = $this->mailparticipantdata;
2437  $data = [];
2438  if (trim($messagetext ?? "")) {
2439  if (!$this->hasAnonymizedResults()) {
2440  $data = ilObjUser::_getUserData(array($a_user_id));
2441  $data = $data[0];
2442  }
2443  foreach ($placeholders as $key => $mapping) {
2444  if ($this->hasAnonymizedResults()) { // #16480
2445  $messagetext = str_replace('[' . $key . ']', '', $messagetext);
2446  } else {
2447  $messagetext = str_replace('[' . $key . ']', trim($data[$mapping] ?? ""), $messagetext);
2448  }
2449  }
2450  $ntf->setIntroductionDirect($messagetext);
2451  } else {
2452  $ntf->setIntroductionLangId('survey_notification_finished_introduction');
2453  }
2454 
2455  // 360°? add appraisee data
2456  if ($a_appr_id) {
2457  $ntf->addAdditionalInfo(
2458  'survey_360_appraisee',
2460  );
2461  }
2462 
2463  $active_id = $this->getActiveID($a_user_id, $a_anonymize_id, $a_appr_id);
2464  if ($active_id) { // 43908
2465  $ntf->addAdditionalInfo(
2466  'results',
2467  $this->getParticipantTextResults($active_id),
2468  true
2469  );
2470  }
2471 
2472  $ntf->setGotoLangId('survey_notification_tutor_link');
2473  $ntf->setReasonLangId('survey_notification_finished_reason');
2474 
2475  $recipient = trim($recipient ?? "");
2476  $user_id = (int) ilObjUser::_lookupId($recipient);
2477  if ($user_id > 0) {
2478  $ntf->sendMailAndReturnRecipients([$user_id]);
2479  } else {
2480  $user_ids = ilObjUser::getUserIdsByEmail($recipient);
2481  if (count($user_ids) > 0) {
2482  $ntf->sendMailAndReturnRecipients([current($user_ids)]);
2483  }
2484  }
2485  /* note: this block is replace by the single line above
2486  since the UI asks for account names and the "e-mail" fallback leads
2487  to strange issues like multiple mails. Also the test case has been
2488  adopted, see https://mantis.ilias.de/view.php?id=36327
2489  if (is_numeric($recipient)) {
2490  $lng = $ntf->getUserLanguage((int) $recipient);
2491  $ntf->sendMailAndReturnRecipients([(int) $recipient]);
2492  } else {
2493  $recipient = trim($recipient);
2494  $user_ids = ilObjUser::getUserIdsByEmail($recipient);
2495  if (empty($user_ids)) {
2496  $ntf->sendMailAndReturnRecipients([ilObjUser::_lookupId($recipient)]);
2497  } else {
2498  foreach ($user_ids as $user_id) {
2499  $lng = $ntf->getUserLanguage($user_id);
2500  $ntf->sendMailAndReturnRecipients(array($user_id));
2501  }
2502  }
2503  }*/
2504  }
2505  }
2506 
2507  protected function getParticipantTextResults(
2508  int $active_id
2509  ): string {
2510  $textresult = "";
2511  $userResults = $this->getUserSpecificResults(array($active_id));
2512  $questions = $this->getSurveyQuestions(true);
2513  $questioncounter = 1;
2514  foreach ($questions as $question_id => $question_data) {
2515  $textresult .= $questioncounter++ . ". " . $question_data["title"] . "\n";
2516  $found = $userResults[$question_id][$active_id];
2517  $text = "";
2518  if (is_array($found)) {
2519  $text = implode("\n", $found);
2520  } else {
2521  $text = $found;
2522  }
2523  if ($text === '') {
2524  $text = self::getSurveySkippedValue();
2525  }
2526  $text = str_replace("<br />", "\n", $text);
2527  $textresult .= $text . "\n\n";
2528  }
2529  return $textresult;
2530  }
2531 
2536  public function getActiveID(
2537  int $user_id,
2538  string $anonymize_id,
2539  int $appr_id
2540  ): ?int {
2541  $ilDB = $this->db;
2542 
2543  // #15031 - should not matter if code was used by registered or anonymous (each code must be unique)
2544  if ($anonymize_id) {
2545  $result = $ilDB->queryF(
2546  "SELECT finished_id FROM svy_finished" .
2547  " WHERE survey_fi = %s AND anonymous_id = %s AND appr_id = %s",
2548  array('integer','text','integer'),
2549  array($this->getSurveyId(), $anonymize_id, $appr_id)
2550  );
2551  } else {
2552  $result = $ilDB->queryF(
2553  "SELECT finished_id FROM svy_finished" .
2554  " WHERE survey_fi = %s AND user_fi = %s AND appr_id = %s",
2555  array('integer','integer','integer'),
2556  array($this->getSurveyId(), $user_id, $appr_id)
2557  );
2558  }
2559  if ($result->numRows() === 0) {
2560  return null;
2561  } else {
2562  $row = $ilDB->fetchAssoc($result);
2563  return (int) $row["finished_id"];
2564  }
2565  }
2566 
2571  public function getLastActivePage(
2572  int $active_id
2573  ): ?int {
2574  $ilDB = $this->db;
2575  $result = $ilDB->queryF(
2576  "SELECT lastpage FROM svy_finished WHERE finished_id = %s",
2577  array('integer'),
2578  array($active_id)
2579  );
2580  if ($row = $ilDB->fetchAssoc($result)) {
2581  return (int) $row["lastpage"];
2582  }
2583  return null;
2584  }
2585 
2594  public function checkConstraint(
2595  array $constraint_data,
2596  ?array $working_data
2597  ): bool {
2598  if (!is_array($working_data) || count($working_data) === 0) {
2599  return 0;
2600  }
2601 
2602  if ((count($working_data) === 1) and (strcmp($working_data[0]["value"], "") === 0)) {
2603  return 0;
2604  }
2605 
2606  $found = false;
2607  foreach ($working_data as $data) {
2608  switch ($constraint_data["short"]) {
2609  case "<":
2610  if ($data["value"] < $constraint_data["value"]) {
2611  $found = true;
2612  }
2613  break;
2614 
2615  case "<=":
2616  if ($data["value"] <= $constraint_data["value"]) {
2617  $found = true;
2618  }
2619  break;
2620 
2621  case "=":
2622  if ($data["value"] == $constraint_data["value"]) {
2623  $found = true;
2624  }
2625  break;
2626 
2627  case "<>":
2628  if ($data["value"] <> $constraint_data["value"]) {
2629  $found = true;
2630  }
2631  break;
2632 
2633  case ">=":
2634  if ($data["value"] >= $constraint_data["value"]) {
2635  $found = true;
2636  }
2637  break;
2638 
2639  case ">":
2640  if ($data["value"] > $constraint_data["value"]) {
2641  $found = true;
2642  }
2643  break;
2644  }
2645  if ($found) {
2646  break;
2647  }
2648  }
2649 
2650  return (int) $found;
2651  }
2652 
2656  public static function _hasDatasets(
2657  int $survey_id
2658  ): bool {
2659  global $DIC;
2660 
2661  $ilDB = $DIC->database();
2662 
2663  $result = $ilDB->queryF(
2664  "SELECT finished_id FROM svy_finished WHERE survey_fi = %s",
2665  array('integer'),
2666  array($survey_id)
2667  );
2668  return (bool) $result->numRows();
2669  }
2670 
2676  public function getSurveyFinishedIds(): array
2677  {
2678  $ilDB = $this->db;
2679 
2680  $users = array();
2681  $result = $ilDB->queryF(
2682  "SELECT * FROM svy_finished WHERE survey_fi = %s",
2683  array('integer'),
2684  array($this->getSurveyId())
2685  );
2686  if ($result->numRows()) {
2687  while ($row = $ilDB->fetchAssoc($result)) {
2688  $users[] = (int) $row["finished_id"];
2689  }
2690  }
2691  return $users;
2692  }
2693 
2699  public function getUserSpecificResults(
2700  array $finished_ids
2701  ): array {
2702  $evaluation = array();
2703 
2704  foreach (array_keys($this->getSurveyQuestions()) as $question_id) {
2705  // get question instance
2706  $question_type = SurveyQuestion::_getQuestionType($question_id);
2707  SurveyQuestion::_includeClass($question_type);
2708  $question = new $question_type();
2709  $question->loadFromDb($question_id);
2710 
2711  $q_eval = SurveyQuestion::_instanciateQuestionEvaluation($question_id, $finished_ids);
2712  $q_res = $q_eval->getResults();
2713 
2714  $data = array();
2715  foreach ($finished_ids as $user_id) {
2716  $data[$user_id] = $q_eval->parseUserSpecificResults($q_res, $user_id);
2717  }
2718 
2719  $evaluation[$question_id] = $data;
2720  }
2721 
2722  return $evaluation;
2723  }
2724 
2729  public function getUserDataFromActiveId(
2730  int $active_id,
2731  bool $force_non_anonymous = false
2732  ): array {
2733  $ilDB = $this->db;
2734 
2735  $surveySetting = new ilSetting("survey");
2736  $use_anonymous_id = $surveySetting->get("use_anonymous_id");
2737  $result = $ilDB->queryF(
2738  "SELECT * FROM svy_finished WHERE finished_id = %s",
2739  array('integer'),
2740  array($active_id)
2741  );
2742  $row = array();
2743  $foundrows = $result->numRows();
2744  if ($foundrows) {
2745  $row = $ilDB->fetchAssoc($result);
2746  }
2747  $name = ($use_anonymous_id) ? $row["anonymous_id"] : $this->lng->txt("anonymous");
2748  $userdata = array(
2749  "fullname" => $name,
2750  "sortname" => $name,
2751  "firstname" => "",
2752  "lastname" => "",
2753  "login" => "",
2754  "gender" => "",
2755  "active_id" => (string) $active_id
2756  );
2757  if ($foundrows) {
2758  if (($row["user_fi"] > 0) &&
2759  (($row["user_fi"] != ANONYMOUS_USER_ID &&
2760  !$this->hasAnonymizedResults() &&
2761  !$this->get360Mode()) || // 360° uses ANONYMIZE_CODE_ALL which is wrong - see ilObjSurveyGUI::afterSave()
2762  $force_non_anonymous)) {
2763  if (ilObjUser::_lookupLogin($row["user_fi"]) === '') {
2764  $userdata["fullname"] = $userdata["sortname"] = $this->lng->txt("deleted_user");
2765  } else {
2766  $user = new ilObjUser($row["user_fi"]);
2767  $userdata['usr_id'] = $row['user_fi'];
2768  $userdata["fullname"] = $user->getFullname();
2769  $gender = $user->getGender();
2770  if (strlen($gender) === 1) {
2771  $gender = $this->lng->txt("gender_$gender");
2772  }
2773  $userdata["gender"] = $gender;
2774  $userdata["firstname"] = $user->getFirstname();
2775  $userdata["lastname"] = $user->getLastname();
2776  $userdata["sortname"] = $user->getLastname() . ", " . $user->getFirstname();
2777  $userdata["login"] = $user->getLogin();
2778  }
2779  }
2780  if ($row["user_fi"] == 0 || $row["user_fi"] == ANONYMOUS_USER_ID) {
2781  $code = $this->code_manager->getByUserKey((string) $row["anonymous_id"]);
2782  if (!is_null($code) && $this->feature_config->usesAppraisees()) {
2783  $userdata["firstname"] = $code->getFirstName();
2784  $userdata["lastname"] = $code->getLastName();
2785  $userdata["sortname"] = $code->getLastName() . ", " . $code->getFirstName();
2786  }
2787  }
2788  }
2789  return $userdata;
2790  }
2791 
2796  public function getEvaluationByUser(
2797  array $questions,
2798  int $active_id
2799  ): array {
2800  $ilDB = $this->db;
2801 
2802  // collect all answers
2803  $answers = array();
2804  $result = $ilDB->queryF(
2805  "SELECT * FROM svy_answer WHERE active_fi = %s",
2806  array('integer'),
2807  array($active_id)
2808  );
2809  while ($row = $ilDB->fetchAssoc($result)) {
2810  if (!is_array($answers[$row["question_fi"]])) {
2811  $answers[$row["question_fi"]] = array();
2812  }
2813  $answers[$row["question_fi"]][] = $row;
2814  }
2815  $userdata = $this->getUserDataFromActiveId($active_id);
2816  $resultset = array(
2817  "name" => $userdata["fullname"],
2818  "firstname" => $userdata["firstname"],
2819  "lastname" => $userdata["lastname"],
2820  "login" => $userdata["login"],
2821  "gender" => $userdata["gender"],
2822  "answers" => array()
2823  );
2824  foreach ($questions as $key => $question) {
2825  if (array_key_exists($key, $answers)) {
2826  $resultset["answers"][$key] = $answers[$key];
2827  } else {
2828  $resultset["answers"][$key] = array();
2829  }
2830  sort($resultset["answers"][$key]);
2831  }
2832  return $resultset;
2833  }
2834 
2839  public function getQuestionsTable(
2840  array $arrFilter
2841  ): array {
2842  $ilDB = $this->db;
2843  $where = "";
2844  if (array_key_exists('title', $arrFilter) && strlen($arrFilter['title'])) {
2845  $where .= " AND " . $ilDB->like('svy_question.title', 'text', "%%" . $arrFilter['title'] . "%%");
2846  }
2847  if (array_key_exists('description', $arrFilter) && strlen($arrFilter['description'])) {
2848  $where .= " AND " . $ilDB->like('svy_question.description', 'text', "%%" . $arrFilter['description'] . "%%");
2849  }
2850  if (array_key_exists('author', $arrFilter) && strlen($arrFilter['author'])) {
2851  $where .= " AND " . $ilDB->like('svy_question.author', 'text', "%%" . $arrFilter['author'] . "%%");
2852  }
2853  if (array_key_exists('type', $arrFilter) && strlen($arrFilter['type'])) {
2854  $where .= " AND svy_qtype.type_tag = " . $ilDB->quote($arrFilter['type'], 'text');
2855  }
2856  if (array_key_exists('spl', $arrFilter) && strlen($arrFilter['spl'])) {
2857  $where .= " AND svy_question.obj_fi = " . $ilDB->quote($arrFilter['spl'], 'integer');
2858  }
2859 
2860  $spls = $this->getAvailableQuestionpools(true, false, false);
2861  $forbidden = " AND " . $ilDB->in('svy_question.obj_fi', array_keys($spls), false, 'integer');
2862  $forbidden .= " AND svy_question.complete = " . $ilDB->quote("1", 'text');
2863  $existing = "";
2864  $existing_questions = $this->getExistingQuestions();
2865  if (count($existing_questions)) {
2866  $existing = " AND " . $ilDB->in('svy_question.question_id', $existing_questions, true, 'integer');
2867  }
2868 
2870 
2871  $query_result = $ilDB->query("SELECT svy_question.*, svy_qtype.type_tag, svy_qtype.plugin, object_reference.ref_id" .
2872  " FROM svy_question, svy_qtype, object_reference" .
2873  " WHERE svy_question.original_id IS NULL" . $forbidden . $existing .
2874  " AND svy_question.obj_fi = object_reference.obj_id AND svy_question.tstamp > 0" .
2875  " AND svy_question.questiontype_fi = svy_qtype.questiontype_id " . $where);
2876 
2877  $rows = array();
2878  if ($query_result->numRows()) {
2879  while ($row = $ilDB->fetchAssoc($query_result)) {
2880  if (array_key_exists('spl_txt', $arrFilter) && strlen($arrFilter['spl_txt'])) {
2881  if (stripos($spls[$row["obj_fi"]], $arrFilter['spl_txt']) === false) {
2882  continue;
2883  }
2884  }
2885 
2886  $row['ttype'] = $trans[$row['type_tag']];
2887  if ($row["plugin"]) {
2888  continue;
2889  } else {
2890  $rows[] = $row;
2891  }
2892  }
2893  }
2894  return $rows;
2895  }
2896 
2901  public function getQuestionblocksTable(
2902  array $arrFilter
2903  ): array {
2904  $ilDB = $this->db;
2905 
2906  $where = "";
2907  if (array_key_exists('title', $arrFilter) && strlen($arrFilter['title'])) {
2908  $where .= " AND " . $ilDB->like('svy_qblk.title', 'text', "%%" . $arrFilter['title'] . "%%");
2909  }
2910 
2911  $query_result = $ilDB->query("SELECT svy_qblk.*, svy_svy.obj_fi FROM svy_qblk , svy_qblk_qst, svy_svy WHERE " .
2912  "svy_qblk.questionblock_id = svy_qblk_qst.questionblock_fi AND svy_svy.survey_id = svy_qblk_qst.survey_fi " .
2913  "$where GROUP BY svy_qblk.questionblock_id, svy_qblk.title, svy_qblk.show_questiontext, svy_qblk.show_blocktitle, " .
2914  "svy_qblk.owner_fi, svy_qblk.tstamp, svy_svy.obj_fi");
2915  $rows = array();
2916  if ($query_result->numRows()) {
2917  $survey_ref_ids = ilUtil::_getObjectsByOperations("svy", "write");
2918  $surveytitles = array();
2919  foreach ($survey_ref_ids as $survey_ref_id) {
2920  $survey_id = ilObject::_lookupObjId($survey_ref_id);
2921  $surveytitles[$survey_id] = ilObject::_lookupTitle($survey_id);
2922  }
2923  while ($row = $ilDB->fetchAssoc($query_result)) {
2924  $questions_array = $this->getQuestionblockQuestions($row["questionblock_id"]);
2925  $counter = 1;
2926  foreach ($questions_array as $key => $value) {
2927  $questions_array[$key] = "$counter. $value";
2928  $counter++;
2929  }
2930  if (strlen($surveytitles[$row["obj_fi"]] ?? "")) { // only questionpools which are not in trash
2931  $rows[$row["questionblock_id"]] = array(
2932  "questionblock_id" => $row["questionblock_id"],
2933  "title" => $row["title"],
2934  "svy" => $surveytitles[$row["obj_fi"]],
2935  "contains" => implode(", ", $questions_array),
2936  "owner" => $row["owner_fi"]
2937  );
2938  }
2939  }
2940  }
2941  return $rows;
2942  }
2943 
2948  public function toXML(): string
2949  {
2950  $a_xml_writer = new ilXmlWriter();
2951  // set xml header
2952  $a_xml_writer->xmlHeader();
2953  $attrs = array(
2954  "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
2955  "xsi:noNamespaceSchemaLocation" => "https://www.ilias.de/download/xsd/ilias_survey_4_2.xsd"
2956  );
2957  $a_xml_writer->xmlStartTag("surveyobject", $attrs);
2958  $attrs = array(
2959  "id" => $this->getSurveyId(),
2960  "title" => $this->getTitle()
2961  );
2962  $a_xml_writer->xmlStartTag("survey", $attrs);
2963 
2964  $a_xml_writer->xmlElement("description", null, $this->getDescription());
2965  $a_xml_writer->xmlElement("author", null, $this->getAuthor());
2966  $a_xml_writer->xmlStartTag("objectives");
2967  $attrs = array(
2968  "label" => "introduction"
2969  );
2970  $this->addMaterialTag($a_xml_writer, $this->getIntroduction(), true, true, $attrs);
2971  $attrs = array(
2972  "label" => "outro"
2973  );
2974  $this->addMaterialTag($a_xml_writer, $this->getOutro(), true, true, $attrs);
2975  $a_xml_writer->xmlEndTag("objectives");
2976 
2977  if ($this->getAnonymize()) {
2978  $attribs = array("enabled" => "1");
2979  } else {
2980  $attribs = array("enabled" => "0");
2981  }
2982  $a_xml_writer->xmlElement("anonymisation", $attribs);
2983  $a_xml_writer->xmlStartTag("restrictions");
2984  if ($this->getAnonymize() === 2) {
2985  $attribs = array("type" => "free");
2986  } else {
2987  $attribs = array("type" => "restricted");
2988  }
2989  $a_xml_writer->xmlElement("access", $attribs);
2990  if ($this->getStartDate()) {
2991  $attrs = array("type" => "date");
2992  preg_match("/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/", $this->getStartDate(), $matches);
2993  $a_xml_writer->xmlElement("startingtime", $attrs, sprintf("%04d-%02d-%02dT%02d:%02d:00", $matches[1], $matches[2], $matches[3], $matches[4], $matches[5]));
2994  }
2995  if ($this->getEndDate()) {
2996  $attrs = array("type" => "date");
2997  preg_match("/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/", $this->getEndDate(), $matches);
2998  $a_xml_writer->xmlElement("endingtime", $attrs, sprintf("%04d-%02d-%02dT%02d:%02d:00", $matches[1], $matches[2], $matches[3], $matches[4], $matches[5]));
2999  }
3000  $a_xml_writer->xmlEndTag("restrictions");
3001 
3002  // constraints
3003  $pages = $this->getSurveyPages();
3004  $hasconstraints = false;
3005  foreach ($pages as $question_array) {
3006  foreach ($question_array as $question) {
3007  if (count($question["constraints"])) {
3008  $hasconstraints = true;
3009  }
3010  }
3011  }
3012 
3013  if ($hasconstraints) {
3014  $a_xml_writer->xmlStartTag("constraints");
3015  foreach ($pages as $question_array) {
3016  foreach ($question_array as $question) {
3017  if (count($question["constraints"])) {
3018  // found constraints
3019  foreach ($question["constraints"] as $constraint) {
3020  $attribs = array(
3021  "sourceref" => $question["question_id"],
3022  "destref" => $constraint["question"],
3023  "relation" => $constraint["short"],
3024  "value" => $constraint["value"],
3025  "conjunction" => $constraint["conjunction"]
3026  );
3027  $a_xml_writer->xmlElement("constraint", $attribs);
3028  }
3029  }
3030  }
3031  }
3032  $a_xml_writer->xmlEndTag("constraints");
3033  }
3034 
3035  // add the rest of the preferences in qtimetadata tags, because there is no correspondent definition in QTI
3036  $a_xml_writer->xmlStartTag("metadata");
3037 
3038  $custom_properties = array();
3039  $custom_properties["evaluation_access"] = $this->getEvaluationAccess();
3040  $custom_properties["status"] = !$this->getOfflineStatus();
3041  $custom_properties["display_question_titles"] = $this->getShowQuestionTitles();
3042  $custom_properties["pool_usage"] = (int) $this->getPoolUsage();
3043 
3044  $custom_properties["own_results_view"] = (int) $this->hasViewOwnResults();
3045  $custom_properties["own_results_mail"] = (int) $this->hasMailOwnResults();
3046  $custom_properties["confirmation_mail"] = (int) $this->hasMailConfirmation();
3047 
3048  $custom_properties["anon_user_list"] = (int) $this->hasAnonymousUserList();
3049  $custom_properties["mode"] = $this->getMode();
3050  $custom_properties["mode_360_self_eval"] = (int) $this->get360SelfEvaluation();
3051  $custom_properties["mode_360_self_rate"] = (int) $this->get360SelfRaters();
3052  $custom_properties["mode_360_self_appr"] = (int) $this->get360SelfAppraisee();
3053  $custom_properties["mode_360_results"] = $this->get360Results();
3054  $custom_properties["mode_skill_service"] = (int) $this->getSkillService();
3055  $custom_properties["mode_self_eval_results"] = $this->getSelfEvaluationResults();
3056 
3057 
3058  // :TODO: skills?
3059 
3060  // reminder/tutor notification are (currently?) not exportable
3061 
3062  foreach ($custom_properties as $label => $value) {
3063  $a_xml_writer->xmlStartTag("metadatafield");
3064  $a_xml_writer->xmlElement("fieldlabel", null, $label);
3065  $a_xml_writer->xmlElement("fieldentry", null, $value);
3066  $a_xml_writer->xmlEndTag("metadatafield");
3067  }
3068 
3069  $a_xml_writer->xmlStartTag("metadatafield");
3070  $a_xml_writer->xmlElement("fieldlabel", null, "SCORM");
3071  $md = new ilMD($this->getId(), 0, $this->getType());
3072  $writer = new ilXmlWriter();
3073  $md->toXML($writer);
3074  $metadata = $writer->xmlDumpMem();
3075  $a_xml_writer->xmlElement("fieldentry", null, $metadata);
3076  $a_xml_writer->xmlEndTag("metadatafield");
3077 
3078  $a_xml_writer->xmlEndTag("metadata");
3079  $a_xml_writer->xmlEndTag("survey");
3080 
3081  $attribs = array("id" => $this->getId());
3082  $a_xml_writer->xmlStartTag("surveyquestions", $attribs);
3083  // add questionblock descriptions
3084  foreach ($pages as $question_array) {
3085  if (count($question_array) > 1) {
3086  $attribs = array("id" => $question_array[0]["question_id"]);
3087  $attribs = array(
3088  "showQuestiontext" => $question_array[0]["questionblock_show_questiontext"],
3089  "showBlocktitle" => $question_array[0]["questionblock_show_blocktitle"],
3090  "compressView" => $question_array[0]["questionblock_compress_view"]
3091  );
3092  $a_xml_writer->xmlStartTag("questionblock", $attribs);
3093  if (strlen($question_array[0]["questionblock_title"])) {
3094  $a_xml_writer->xmlElement("questionblocktitle", null, $question_array[0]["questionblock_title"]);
3095  }
3096  }
3097  foreach ($question_array as $question) {
3098  if (strlen($question["heading"] ?? "")) {
3099  $a_xml_writer->xmlElement("textblock", null, $question["heading"]);
3100  }
3101  $questionObject = self::_instanciateQuestion($question["question_id"]);
3102  //questionObject contains all the fields from the database. (loadFromDb)
3103  //we don't need the value from svy_qst_oblig table, we already have the values from svy_question table.
3104  //if ($questionObject !== FALSE) $questionObject->insertXML($a_xml_writer, FALSE, $obligatory_states[$question["question_id"]]);
3105  if ($questionObject !== false) {
3106  $questionObject->insertXML($a_xml_writer, false);
3107  }
3108  }
3109  if (count($question_array) > 1) {
3110  $a_xml_writer->xmlEndTag("questionblock");
3111  }
3112  }
3113 
3114  $a_xml_writer->xmlEndTag("surveyquestions");
3115  $a_xml_writer->xmlEndTag("surveyobject");
3116  $xml = $a_xml_writer->xmlDumpMem(false);
3117  return $xml;
3118  }
3119 
3124  public static function _instanciateQuestion(
3125  int $question_id
3126  ): ?SurveyQuestion {
3127  if ($question_id < 1) {
3128  return null;
3129  }
3130  $question_type = SurveyQuestion::_getQuestionType($question_id);
3131  if ($question_type === '') {
3132  return null;
3133  }
3134  SurveyQuestion::_includeClass($question_type);
3135  $question = new $question_type();
3136  $question->loadFromDb($question_id);
3137  return $question;
3138  }
3139 
3145  public function locateImportFiles(
3146  string $a_dir
3147  ): ?array {
3148  if (!is_dir($a_dir) || is_int(strpos($a_dir, ".."))) {
3149  return null;
3150  }
3151  $importDirectory = "";
3152  $xmlFile = "";
3153 
3154  $current_dir = opendir($a_dir);
3155  $files = array();
3156  while ($entryname = readdir($current_dir)) {
3157  $files[] = $entryname;
3158  }
3159 
3160  foreach ($files as $file) {
3161  if (is_dir($a_dir . "/" . $file) and ($file !== "." and $file !== "..")) {
3162  // found directory created by zip
3163  $importDirectory = $a_dir . "/" . $file;
3164  }
3165  }
3166  closedir($current_dir);
3167  if ($importDirectory !== '') {
3168  // find the xml file
3169  $current_dir = opendir($importDirectory);
3170  $files = array();
3171  while ($entryname = readdir($current_dir)) {
3172  $files[] = $entryname;
3173  }
3174  foreach ($files as $file) {
3175  if (is_file($importDirectory . "/" . $file) &&
3176  ($file !== "." && $file !== "..") &&
3177  (preg_match("/^[0-9]{10}__[0-9]+__(svy_)*[0-9]+\.[A-Za-z]{1,3}$/", $file) ||
3178  preg_match("/^[0-9]{10}__[0-9]+__(survey__)*[0-9]+\.[A-Za-z]{1,3}$/", $file))) {
3179  // found xml file
3180  $xmlFile = $importDirectory . "/" . $file;
3181  }
3182  }
3183  }
3184  return array("dir" => $importDirectory, "xml" => $xmlFile);
3185  }
3186 
3194  public function importObject(
3195  array $file_info,
3196  int $svy_qpl_id
3197  ): string {
3198  if ($svy_qpl_id < 1) {
3199  $svy_qpl_id = -1;
3200  }
3201  // check if file was uploaded
3202  $source = $file_info["tmp_name"];
3203  $error = "";
3204  if (($source === 'none') || (!$source) || $file_info["error"] > UPLOAD_ERR_OK) {
3205  $error = $this->lng->txt("import_no_file_selected");
3206  }
3207  // check correct file type
3208  $isXml = false;
3209  $isZip = false;
3210  if ((strcmp($file_info["type"], "text/xml") === 0) || (strcmp($file_info["type"], "application/xml") === 0)) {
3211  $this->svy_log->debug("isXML");
3212  $isXml = true;
3213  }
3214  // too many different mime-types, so we use the suffix
3215  $suffix = pathinfo($file_info["name"]);
3216  if (strcmp(strtolower($suffix["extension"]), "zip") === 0) {
3217  $this->svy_log->debug("isZip");
3218  $isZip = true;
3219  }
3220  if (!$isXml && !$isZip) {
3221  $error = $this->lng->txt("import_wrong_file_type");
3222  $this->svy_log->debug("Survey: Import error. Filetype was \"" . $file_info["type"] . "\"");
3223  }
3224  if ($error === '') {
3225  // import file as a survey
3226  $import_dir = $this->getImportDirectory();
3227  $import_subdir = "";
3228  $importfile = "";
3229  if ($isZip) {
3230  $importfile = $import_dir . "/" . $file_info["name"];
3231  ilFileUtils::moveUploadedFile($source, $file_info["name"], $importfile);
3232  $this->domain->resources()->zip()->unzipFile($importfile);
3233  $found = $this->locateImportFiles($import_dir);
3234  if (!((strlen($found["dir"]) > 0) && (strlen($found["xml"]) > 0))) {
3235  $error = $this->lng->txt("wrong_import_file_structure");
3236  return $error;
3237  }
3238  $importfile = $found["xml"];
3239  $import_subdir = $found["dir"];
3240  } else {
3241  $importfile = tempnam($import_dir, "survey_import");
3242  ilFileUtils::moveUploadedFile($source, $file_info["name"], $importfile);
3243  }
3244 
3245  $this->svy_log->debug("Import file = $importfile");
3246  $this->svy_log->debug("Import subdir = $import_subdir");
3247 
3248  $fh = fopen($importfile, 'rb');
3249  if (!$fh) {
3250  $error = $this->lng->txt("import_error_opening_file");
3251  return $error;
3252  }
3253  $xml = fread($fh, filesize($importfile));
3254  $result = fclose($fh);
3255  if (!$result) {
3256  $error = $this->lng->txt("import_error_closing_file");
3257  return $error;
3258  }
3259 
3260  $this->import_manager->clearMobs();
3261  if (strpos($xml, "questestinterop")) {
3262  throw new ilInvalidSurveyImportFileException("Unsupported survey version (< 3.8) found.");
3263  } else {
3264  $this->svy_log->debug("survey id = " . $this->getId());
3265  $this->svy_log->debug("question pool id = " . $svy_qpl_id);
3266 
3267  $imp = new ilImport();
3268  $config = $imp->getConfig("Modules/Survey");
3269  $config->setQuestionPoolID($svy_qpl_id);
3270  $imp->getMapping()->addMapping("Modules/Survey", "svy", 0, $this->getId());
3271  $imp->importFromDirectory($import_subdir, "svy", "Modules/Survey");
3272  $this->svy_log->debug("config(Modules/survey)->getQuestionPoolId =" . $config->getQuestionPoolID());
3273  }
3274  }
3275  return $error;
3276  }
3277 
3278  public function cloneObject(int $target_id, int $copy_id = 0, bool $omit_tree = false): ?ilObject
3279  {
3280  $ilDB = $this->db;
3281 
3282  $this->loadFromDb();
3283 
3284  //survey mode
3285  $svy_type = $this->getMode();
3286 
3288  $newObj = parent::cloneObject($target_id, $copy_id, $omit_tree);
3289  $this->cloneMetaData($newObj);
3290  $newObj->updateMetaData();
3291 
3292  $newObj->setAuthor($this->getAuthor());
3293  $newObj->setIntroduction($this->getIntroduction());
3294  $newObj->setOutro($this->getOutro());
3295  $newObj->setEvaluationAccess($this->getEvaluationAccess());
3296  $newObj->setStartDate($this->getStartDate());
3297  $newObj->setEndDate($this->getEndDate());
3298  $newObj->setAnonymize($this->getAnonymize());
3299  $newObj->setShowQuestionTitles($this->getShowQuestionTitles());
3300  $newObj->setPoolUsage($this->getPoolUsage());
3301  $newObj->setViewOwnResults($this->hasViewOwnResults());
3302  $newObj->setMailOwnResults($this->hasMailOwnResults());
3303  $newObj->setMailConfirmation($this->hasMailConfirmation());
3304  $newObj->setAnonymousUserList($this->hasAnonymousUserList());
3305 
3306  $newObj->setMode($this->getMode());
3307  $newObj->set360SelfEvaluation($this->get360SelfEvaluation());
3308  $newObj->set360SelfAppraisee($this->get360SelfAppraisee());
3309  $newObj->set360SelfRaters($this->get360SelfRaters());
3310  $newObj->set360Results($this->get360Results());
3311  $newObj->setSkillService($this->getSkillService());
3312 
3313  // reminder/notification
3314  $newObj->setReminderStatus($this->getReminderStatus());
3315  $newObj->setReminderStart($this->getReminderStart());
3316  $newObj->setReminderEnd($this->getReminderEnd());
3317  $newObj->setReminderFrequency($this->getReminderFrequency());
3318  $newObj->setReminderTarget($this->getReminderTarget());
3319  $newObj->setReminderTemplate($this->getReminderTemplate());
3320  // reminder_last_sent must not be copied!
3321  $newObj->setTutorNotificationStatus($this->getTutorNotificationStatus());
3322  $newObj->setTutorNotificationRecipients($this->getTutorNotificationRecipients());
3323  $newObj->setTutorNotificationTarget($this->getTutorNotificationTarget());
3324  $newObj->setTutorResultsStatus($this->getTutorResultsStatus());
3325  $newObj->setTutorResultsRecipients($this->getTutorResultsRecipients());
3326 
3327  $newObj->setMailNotification($this->getMailNotification());
3328  $newObj->setMailAddresses($this->getMailAddresses());
3329  $newObj->setMailParticipantData($this->getMailParticipantData());
3330 
3331  $question_pointer = array();
3332  // clone the questions
3333  $mapping = array();
3334 
3335  foreach ($this->questions as $key => $question_id) {
3337  $question = self::_instanciateQuestion($question_id);
3338  if ($question) { // #10824
3339  $question->id = -1;
3340  $original_id = SurveyQuestion::_getOriginalId($question_id, false);
3341  $question->setObjId($newObj->getId());
3342  $question->saveToDb($original_id);
3343  $newObj->questions[$key] = $question->getId();
3344  $question_pointer[$question_id] = $question->getId();
3345  $mapping[$question_id] = $question->getId();
3346  }
3347  }
3348 
3349  //copy online status if object is not the root copy object
3350  $cp_options = ilCopyWizardOptions::_getInstance($copy_id);
3351 
3352  if (!$cp_options->isRootNode($this->getRefId())) {
3353  $newObj->setOfflineStatus($this->getOfflineStatus());
3354  }
3355 
3356  $newObj->saveToDb();
3357  $newObj->cloneTextblocks($mapping);
3358 
3359  // #14929
3360  if (($svy_type === self::MODE_360 || $svy_type === self::MODE_SELF_EVAL) &&
3361  $this->getSkillService()) {
3362  $src_skills = new ilSurveySkill($this);
3363  $tgt_skills = new ilSurveySkill($newObj);
3364 
3365  foreach ($mapping as $src_qst_id => $tgt_qst_id) {
3366  $qst_skill = $src_skills->getSkillForQuestion($src_qst_id);
3367  if ($qst_skill) {
3368  $tgt_skills->addQuestionSkillAssignment($tgt_qst_id, $qst_skill["base_skill_id"], $qst_skill["tref_id"]);
3369  }
3370  }
3371 
3372  $thresholds = new ilSurveySkillThresholds($this);
3373  $thresholds->cloneTo($newObj, $mapping);
3374  }
3375 
3376  // clone the questionblocks
3377  $questionblocks = array();
3378  $questionblock_questions = array();
3379  $result = $ilDB->queryF(
3380  "SELECT * FROM svy_qblk_qst WHERE survey_fi = %s",
3381  array('integer'),
3382  array($this->getSurveyId())
3383  );
3384  if ($result->numRows() > 0) {
3385  while ($row = $ilDB->fetchAssoc($result)) {
3386  $questionblock_questions[] = $row;
3387  $questionblocks[$row["questionblock_fi"]] = $row["questionblock_fi"];
3388  }
3389  }
3390  // create new questionblocks
3391  foreach ($questionblocks as $key => $value) {
3392  $questionblock = self::_getQuestionblock($key);
3393  $questionblock_id = self::_addQuestionblock(
3394  (string) $questionblock["title"],
3395  (int) $questionblock["owner_fi"],
3396  (bool) $questionblock["show_questiontext"],
3397  (bool) $questionblock["show_blocktitle"],
3398  (bool) $questionblock["compress_view"]
3399  );
3400  $questionblocks[$key] = $questionblock_id;
3401  }
3402  // create new questionblock questions
3403  foreach ($questionblock_questions as $key => $value) {
3404  if ($questionblocks[$value["questionblock_fi"]] &&
3405  $question_pointer[$value["question_fi"]]) {
3406  $next_id = $ilDB->nextId('svy_qblk_qst');
3407  $affectedRows = $ilDB->manipulateF(
3408  "INSERT INTO svy_qblk_qst (qblk_qst_id, survey_fi, questionblock_fi, question_fi) " .
3409  "VALUES (%s, %s, %s, %s)",
3410  array('integer','integer','integer','integer'),
3411  array($next_id, $newObj->getSurveyId(), $questionblocks[$value["questionblock_fi"]], $question_pointer[$value["question_fi"]])
3412  );
3413  }
3414  }
3415 
3416  // clone the constraints
3417  $constraints = self::_getConstraints($this->getSurveyId());
3418  $newConstraints = array();
3419  foreach ($constraints as $key => $constraint) {
3420  if ($question_pointer[$constraint["for_question"]] &&
3421  $question_pointer[$constraint["question"]]) {
3422  if (!array_key_exists($constraint['id'], $newConstraints)) {
3423  $constraint_id = $newObj->addConstraint($question_pointer[$constraint["question"]], $constraint["relation_id"], $constraint["value"], $constraint['conjunction']);
3424  $newConstraints[$constraint['id']] = $constraint_id;
3425  }
3426  $newObj->addConstraintToQuestion($question_pointer[$constraint["for_question"]], $newConstraints[$constraint['id']]);
3427  }
3428  }
3429 
3430  // #16210 - clone LP settings
3431  $obj_settings = new ilLPObjSettings($this->getId());
3432  $obj_settings->cloneSettings($newObj->getId());
3433  unset($obj_settings);
3434 
3435  return $newObj;
3436  }
3437 
3441  public function getTextblock(
3442  int $question_id
3443  ): string {
3444  $ilDB = $this->db;
3445  $result = $ilDB->queryF(
3446  "SELECT * FROM svy_svy_qst WHERE question_fi = %s",
3447  array('integer'),
3448  array($question_id)
3449  );
3450  if ($row = $ilDB->fetchAssoc($result)) {
3451  return $row["heading"] ?? "";
3452  } else {
3453  return "";
3454  }
3455  }
3456 
3462  public function cloneTextblocks(
3463  array $mapping
3464  ): void {
3465  foreach ($mapping as $original_id => $new_id) {
3466  $textblock = $this->getTextblock($original_id);
3467  $this->saveHeading(ilUtil::stripSlashes($textblock, true, ilObjAdvancedEditing::_getUsedHTMLTagsAsString("survey")), $new_id);
3468  }
3469  }
3470 
3477  public function createExportDirectory(): void
3478  {
3479  $svy_data_dir = ilFileUtils::getDataDir() . "/svy_data";
3480  ilFileUtils::makeDir($svy_data_dir);
3481  if (!is_writable($svy_data_dir)) {
3482  throw new ilSurveyException("Survey Data Directory (" . $svy_data_dir . ") not writeable.");
3483  }
3484 
3485  // create learning module directory (data_dir/lm_data/lm_<id>)
3486  $svy_dir = $svy_data_dir . "/svy_" . $this->getId();
3487  ilFileUtils::makeDir($svy_dir);
3488  if (!is_dir($svy_dir)) {
3489  throw new ilSurveyException("Creation of Survey Directory failed.");
3490  }
3491  // create Export subdirectory (data_dir/lm_data/lm_<id>/Export)
3492  $export_dir = $svy_dir . "/export";
3493  ilFileUtils::makeDir($export_dir);
3494  if (!is_dir($export_dir)) {
3495  throw new ilSurveyException("Creation of Export Directory failed.");
3496  }
3497  }
3498 
3502  public function getExportDirectory(): string
3503  {
3504  $export_dir = ilFileUtils::getDataDir() . "/svy_data" . "/svy_" . $this->getId() . "/export";
3505 
3506  return $export_dir;
3507  }
3508 
3515  public function createImportDirectory(): void
3516  {
3517  $svy_data_dir = ilFileUtils::getDataDir() . "/svy_data";
3518  ilFileUtils::makeDir($svy_data_dir);
3519 
3520  if (!is_writable($svy_data_dir)) {
3521  throw new ilSurveyException("Survey Data Directory (" . $svy_data_dir . ") not writeable.");
3522  }
3523 
3524  // create test directory (data_dir/svy_data/svy_<id>)
3525  $svy_dir = $svy_data_dir . "/svy_" . $this->getId();
3526  ilFileUtils::makeDir($svy_dir);
3527  if (!is_dir($svy_dir)) {
3528  throw new ilSurveyException("Creation of Survey Directory failed.");
3529  }
3530 
3531  // create import subdirectory (data_dir/svy_data/svy_<id>/import)
3532  $import_dir = $svy_dir . "/import";
3533  ilFileUtils::makeDir($import_dir);
3534  if (!is_dir($import_dir)) {
3535  throw new ilSurveyException("Creation of Import Directory failed.");
3536  }
3537  }
3538 
3543  public function getImportDirectory(): string
3544  {
3545  $import_dir = ilFileUtils::getDataDir() . "/svy_data" .
3546  "/svy_" . $this->getId() . "/import";
3547  if (!is_dir($import_dir)) {
3548  ilFileUtils::makeDirParents($import_dir);
3549  }
3550  if (is_dir($import_dir)) {
3551  return $import_dir;
3552  } else {
3553  return "";
3554  }
3555  }
3556 
3562  public function saveHeading(
3563  string $heading,
3564  int $insertbefore
3565  ): void {
3566  $ilDB = $this->db;
3567  if ($heading) {
3568  $ilDB->manipulateF(
3569  "UPDATE svy_svy_qst SET heading=%s WHERE survey_fi=%s AND question_fi=%s",
3570  array('text','integer','integer'),
3571  array($heading, $this->getSurveyId(), $insertbefore)
3572  );
3573  } else {
3574  $ilDB->manipulateF(
3575  "UPDATE svy_svy_qst SET heading=%s WHERE survey_fi=%s AND question_fi=%s",
3576  array('text','integer','integer'),
3577  array(null, $this->getSurveyId(), $insertbefore)
3578  );
3579  }
3580  }
3581 
3586  public function isAnonymizedParticipant(string $key): bool
3587  {
3588  $ilDB = $this->db;
3589 
3590  $result = $ilDB->queryF(
3591  "SELECT finished_id FROM svy_finished WHERE anonymous_id = %s AND survey_fi = %s",
3592  array('text','integer'),
3593  array($key, $this->getSurveyId())
3594  );
3595  return $result->numRows() === 1;
3596  }
3597 
3607  public function getSurveyCodesForExport(
3608  array $a_codes = null,
3609  array $a_ids = null
3610  ): string {
3611  $ilDB = $this->db;
3612  $ilUser = $this->user;
3613  $lng = $this->lng;
3614 
3615  $sql = "SELECT svy_anonymous.*, svy_finished.state" .
3616  " FROM svy_anonymous" .
3617  " LEFT JOIN svy_finished ON (svy_anonymous.survey_key = svy_finished.anonymous_id)" .
3618  " WHERE svy_anonymous.survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
3619  " AND svy_anonymous.user_key IS NULL";
3620 
3621  if ($a_codes) {
3622  $sql .= " AND " . $ilDB->in("svy_anonymous.survey_key", $a_codes, "", "text");
3623  } elseif ($a_ids) {
3624  $sql .= " AND " . $ilDB->in("svy_anonymous.anonymous_id", $a_ids, "", "text");
3625  }
3626 
3627  $export = array();
3628 
3629  // #14905
3630  $titles = array();
3631  $titles[] = '"' . $lng->txt("survey_code") . '"';
3632  $titles[] = '"' . $lng->txt("email") . '"';
3633  $titles[] = '"' . $lng->txt("lastname") . '"';
3634  $titles[] = '"' . $lng->txt("firstname") . '"';
3635  $titles[] = '"' . $lng->txt("create_date") . '"';
3636  $titles[] = '"' . $lng->txt("used") . '"';
3637  $titles[] = '"' . $lng->txt("mail_sent_short") . '"';
3638  $titles[] = '"' . $lng->txt("survey_code_url") . '"';
3639  $export[] = implode(";", $titles);
3640 
3641  $result = $ilDB->query($sql);
3642  $default_lang = $ilUser->getPref("survey_code_language");
3643  while ($row = $ilDB->fetchAssoc($result)) {
3644  $item = array();
3645  $item[] = $row["survey_key"];
3646 
3647  if ($row["externaldata"]) {
3648  $ext = unserialize((string) $row["externaldata"], ['allowed_classes' => false]);
3649  $item[] = $ext["email"];
3650  $item[] = $ext["lastname"];
3651  $item[] = $ext["firstname"];
3652  } else {
3653  $item[] = "";
3654  $item[] = "";
3655  $item[] = "";
3656  }
3657 
3658  // No relative (today, tomorrow...) dates in export.
3659  $date = new ilDateTime($row['tstamp'], IL_CAL_UNIX);
3660  $item[] = $date->get(IL_CAL_DATETIME);
3661 
3662  $item[] = ($this->isSurveyCodeUsed($row["survey_key"])) ? 1 : 0;
3663  $item[] = ($row["sent"]) ? 1 : 0;
3664 
3665  $params = array("accesscode" => $row["survey_key"]);
3666  if ($default_lang) {
3667  $params["lang"] = $default_lang;
3668  }
3669  $item[] = ilLink::_getLink($this->getRefId(), "svy", $params);
3670 
3671  $export[] = '"' . implode('";"', $item) . '"';
3672  }
3673  return implode("\n", $export);
3674  }
3675 
3681  public function getSurveyCodesTableData(
3682  array $ids = null,
3683  string $lang = null
3684  ): array {
3685  $ilDB = $this->db;
3686 
3687  $codes = array();
3688 
3689  $sql = "SELECT svy_anonymous.*, svy_finished.state" .
3690  " FROM svy_anonymous" .
3691  " LEFT JOIN svy_finished ON (svy_anonymous.survey_key = svy_finished.anonymous_id)" .
3692  " WHERE svy_anonymous.survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") /*.
3693  " AND svy_anonymous.user_key IS NULL" */; // #15860
3694 
3695  if ($ids) {
3696  $sql .= " AND " . $ilDB->in("svy_anonymous.anonymous_id", $ids, "", "integer");
3697  }
3698 
3699  $sql .= " ORDER BY tstamp, survey_key ASC";
3700  $result = $ilDB->query($sql);
3701  if ($result->numRows() > 0) {
3702  while ($row = $ilDB->fetchAssoc($result)) {
3703  $href = "";
3704  $used = false;
3705  if ($this->isSurveyCodeUsed($row["survey_key"])) {
3706  $used = true;
3707  } else {
3708  $params = array("accesscode" => $row["survey_key"]);
3709  if ($lang) {
3710  $params["lang"] = $lang;
3711  }
3712  $href = ilLink::_getLink($this->getRefId(), "svy", $params);
3713  }
3714 
3715 
3716  $item = array(
3717  'id' => $row["anonymous_id"],
3718  'code' => $row["survey_key"],
3719  'date' => $row["tstamp"],
3720  'used' => $used,
3721  'sent' => $row['sent'],
3722  'href' => $href,
3723  'email' => '',
3724  'last_name' => '',
3725  'first_name' => ''
3726  );
3727 
3728  if ($row["externaldata"]) {
3729  $ext = unserialize((string) $row["externaldata"], ['allowed_classes' => false]);
3730  $item['email'] = $ext['email'] ?? "";
3731  $item['last_name'] = $ext['lastname'] ?? "";
3732  $item['first_name'] = $ext['firstname'] ?? "";
3733  }
3734 
3735  $codes[] = $item;
3736  }
3737  }
3738  return $codes;
3739  }
3740 
3744  public function isSurveyCodeUsed(
3745  string $code
3746  ): bool {
3747  $ilDB = $this->db;
3748  $result = $ilDB->queryF(
3749  "SELECT finished_id FROM svy_finished WHERE survey_fi = %s AND anonymous_id = %s",
3750  array('integer','text'),
3751  array($this->getSurveyId(), $code)
3752  );
3753  return $result->numRows() > 0;
3754  }
3755 
3759  public function isSurveyCodeUnique(
3760  string $code
3761  ): bool {
3762  $ilDB = $this->db;
3763  $result = $ilDB->queryF(
3764  "SELECT anonymous_id FROM svy_anonymous WHERE survey_fi = %s AND survey_key = %s",
3765  array('integer','text'),
3766  array($this->getSurveyId(), $code)
3767  );
3768  return !(($result->numRows() > 0));
3769  }
3770 
3774  public function importSurveyCode(
3775  string $a_anonymize_key,
3776  int $a_created,
3777  array $a_data
3778  ): void {
3779  $ilDB = $this->db;
3780 
3781  $next_id = $ilDB->nextId('svy_anonymous');
3782  $ilDB->manipulateF(
3783  "INSERT INTO svy_anonymous (anonymous_id, survey_key, survey_fi, externaldata, tstamp) " .
3784  "VALUES (%s, %s, %s, %s, %s)",
3785  array('integer','text','integer','text','integer'),
3786  array($next_id, $a_anonymize_key, $this->getSurveyId(), serialize($a_data), $a_created)
3787  );
3788  }
3789 
3793  public function sendCodes(
3794  int $not_sent,
3795  string $subject,
3796  string $message,
3797  string $lang
3798  ): void {
3799  /*
3800  * 0 = all
3801  * 1 = not sent
3802  * 2 = finished
3803  * 3 = not finished
3804  */
3805  $check_finished = ($not_sent > 1);
3806 
3807 
3808  $mail = new ilMail(ANONYMOUS_USER_ID);
3809  $recipients = $this->getExternalCodeRecipients($check_finished);
3810  foreach ($recipients as $data) {
3811  if ($data['email'] && $data['code']) {
3812  $do_send = false;
3813  switch ($not_sent) {
3814  case 1:
3815  $do_send = !(bool) $data['sent'];
3816  break;
3817 
3818  case 2:
3819  $do_send = $data['finished'];
3820  break;
3821 
3822  case 3:
3823  $do_send = !$data['finished'];
3824  break;
3825 
3826  default:
3827  $do_send = true;
3828  break;
3829  }
3830  if ($do_send) {
3831  // build text
3832  $messagetext = $message;
3833  $url = ilLink::_getLink(
3834  $this->getRefId(),
3835  "svy",
3836  array(
3837  "accesscode" => $data["code"],
3838  "lang" => $lang
3839  )
3840  );
3841  $messagetext = str_replace('[url]', $url, $messagetext);
3842  foreach ($data as $key => $value) {
3843  $messagetext = str_replace('[' . $key . ']', $value, $messagetext);
3844  }
3845 
3846  // send mail
3847  $mail->enqueue(
3848  $data['email'], // to
3849  "", // cc
3850  "", // bcc
3851  $subject, // subject
3852  $messagetext, // message
3853  array() // attachments
3854  );
3855  }
3856  }
3857  }
3858 
3859  $ilDB = $this->db;
3860  $ilDB->manipulateF(
3861  "UPDATE svy_anonymous SET sent = %s WHERE survey_fi = %s AND externaldata IS NOT NULL",
3862  array('integer','integer'),
3863  array(1, $this->getSurveyId())
3864  );
3865  }
3866 
3870  public function getExternalCodeRecipients(
3871  bool $a_check_finished = false
3872  ): array {
3873  $ilDB = $this->db;
3874  $result = $ilDB->queryF(
3875  "SELECT survey_key code, externaldata, sent FROM svy_anonymous WHERE survey_fi = %s",
3876  array('integer'),
3877  array($this->getSurveyId())
3878  );
3879  $res = array();
3880  while ($row = $ilDB->fetchAssoc($result)) {
3881  if (!$row['externaldata']) {
3882  continue;
3883  }
3884 
3885  $externaldata = unserialize((string) $row['externaldata'], ['allowed_classes' => false]);
3886  if (!$externaldata['email']) {
3887  continue;
3888  }
3889 
3890  $externaldata['code'] = $row['code'];
3891  $externaldata['sent'] = $row['sent'];
3892 
3893  if ($a_check_finished) {
3894  #23294
3895  //$externaldata['finished'] = $this->isSurveyCodeUsed($row['code']);
3896  $externaldata['finished'] = $this->isSurveyFinishedByCode($row['code']);
3897  }
3898 
3899  $res[] = $externaldata;
3900  }
3901  return $res;
3902  }
3903 
3910  public function isSurveyFinishedByCode(string $a_code): bool
3911  {
3912  $result = $this->db->queryF(
3913  "SELECT state FROM svy_finished WHERE survey_fi = %s AND anonymous_id = %s",
3914  array('integer','text'),
3915  array($this->getSurveyId(), $a_code)
3916  );
3917 
3918  $row = $this->db->fetchAssoc($result);
3919 
3920  return $row['state'] ?? false;
3921  }
3922 
3926  public function deleteSurveyCode(
3927  string $survey_code
3928  ): void {
3929  $ilDB = $this->db;
3930 
3931  if ($survey_code !== '') {
3932  $affectedRows = $ilDB->manipulateF(
3933  "DELETE FROM svy_anonymous WHERE survey_fi = %s AND survey_key = %s",
3934  array('integer', 'text'),
3935  array($this->getSurveyId(), $survey_code)
3936  );
3937  }
3938  }
3939 
3946  public function getUserAccessCode(int $user_id): string
3947  {
3948  $ilDB = $this->db;
3949  $access_code = "";
3950  $result = $ilDB->queryF(
3951  "SELECT survey_key FROM svy_anonymous WHERE survey_fi = %s AND user_key = %s",
3952  array('integer','text'),
3953  array($this->getSurveyId(), md5($user_id))
3954  );
3955  if ($result->numRows()) {
3956  $row = $ilDB->fetchAssoc($result);
3957  $access_code = $row["survey_key"];
3958  }
3959  return $access_code;
3960  }
3961 
3968  public function saveUserAccessCode(
3969  int $user_id,
3970  string $access_code
3971  ): void {
3972  $ilDB = $this->db;
3973 
3974  // not really sure what to do about ANONYMOUS_USER_ID
3975 
3976  $next_id = $ilDB->nextId('svy_anonymous');
3977  $affectedRows = $ilDB->manipulateF(
3978  "INSERT INTO svy_anonymous (anonymous_id, survey_key, survey_fi, user_key, tstamp) " .
3979  "VALUES (%s, %s, %s, %s, %s)",
3980  array('integer','text', 'integer', 'text', 'integer'),
3981  array($next_id, $access_code, $this->getSurveyId(), md5($user_id), time())
3982  );
3983  }
3984 
3985 
3989  public function getLastAccess(
3990  int $finished_id
3991  ): ?int {
3992  $ilDB = $this->db;
3993 
3994  $result = $ilDB->queryF(
3995  "SELECT tstamp FROM svy_answer WHERE active_fi = %s ORDER BY tstamp DESC",
3996  array('integer'),
3997  array($finished_id)
3998  );
3999  if ($result->numRows()) {
4000  $row = $ilDB->fetchAssoc($result);
4001  return (int) $row["tstamp"];
4002  } else {
4003  $result = $ilDB->queryF(
4004  "SELECT tstamp FROM svy_finished WHERE finished_id = %s",
4005  array('integer'),
4006  array($finished_id)
4007  );
4008  if ($result->numRows()) {
4009  $row = $ilDB->fetchAssoc($result);
4010  return (int) $row["tstamp"];
4011  }
4012  }
4013  return null;
4014  }
4015 
4019  public function prepareTextareaOutput(
4020  string $txt_output
4021  ): string {
4023  }
4024 
4029  public function isHTML(
4030  string $a_text
4031  ): bool {
4032  if (preg_match("/<[^>]*?>/", $a_text)) {
4033  return true;
4034  } else {
4035  return false;
4036  }
4037  }
4038 
4043  public function addMaterialTag(
4044  ilXmlWriter $a_xml_writer,
4045  string $a_material,
4046  bool $close_material_tag = true,
4047  bool $add_mobs = true,
4048  array $attribs = null
4049  ): void {
4050  $a_xml_writer->xmlStartTag("material", $attribs);
4051  $attrs = array(
4052  "type" => "text/plain"
4053  );
4054  if ($this->isHTML($a_material)) {
4055  $attrs["type"] = "text/xhtml";
4056  }
4057  $mattext = ilRTE::_replaceMediaObjectImageSrc($a_material, 0);
4058  $a_xml_writer->xmlElement("mattext", $attrs, $mattext);
4059 
4060  if ($add_mobs) {
4061  $mobs = ilObjMediaObject::_getMobsOfObject("svy:html", $this->getId());
4062  foreach ($mobs as $mob) {
4063  $mob_id = "il_" . IL_INST_ID . "_mob_" . $mob;
4064  if (strpos($mattext, $mob_id) !== false) {
4065  $mob_obj = new ilObjMediaObject($mob);
4066  $imgattrs = array(
4067  "label" => $mob_id,
4068  "uri" => "objects/" . "il_" . IL_INST_ID . "_mob_" . $mob . "/" . $mob_obj->getTitle(),
4069  "type" => "svy:html",
4070  "id" => $this->getId()
4071  );
4072  $a_xml_writer->xmlElement("matimage", $imgattrs, null);
4073  }
4074  }
4075  }
4076  if ($close_material_tag) {
4077  $a_xml_writer->xmlEndTag("material");
4078  }
4079  }
4080 
4088  public function canExportSurveyCode(): bool
4089  {
4090  if ($this->getAnonymize() !== self::ANONYMIZE_OFF) {
4091  if ($this->surveyCodeSecurity === false) {
4092  return true;
4093  }
4094  }
4095  return false;
4096  }
4097 
4098  public function setSurveyId(int $survey_id): void
4099  {
4100  $this->survey_id = $survey_id;
4101  }
4102 
4110  public function getUserData(
4111  array $ids
4112  ): array {
4113  $ilDB = $this->db;
4114 
4115  if (count($ids) === 0) {
4116  return array();
4117  }
4118 
4119  $result = $ilDB->query("SELECT usr_id, login, lastname, firstname FROM usr_data WHERE " . $ilDB->in('usr_id', $ids, false, 'integer') . " ORDER BY login");
4120  $result_array = array();
4121  while ($row = $ilDB->fetchAssoc($result)) {
4122  $result_array[$row["usr_id"]] = $row;
4123  }
4124  return $result_array;
4125  }
4126 
4130  public function getMailNotification(): bool
4131  {
4132  return $this->mailnotification;
4133  }
4134 
4138  public function setMailNotification(bool $a_notification): void
4139  {
4140  $this->mailnotification = $a_notification;
4141  }
4142 
4146  public function getMailAddresses(): string
4147  {
4148  return $this->mailaddresses;
4149  }
4150 
4154  public function setMailAddresses(string $a_addresses): void
4155  {
4156  $this->mailaddresses = $a_addresses;
4157  }
4158 
4162  public function getMailParticipantData(): string
4163  {
4165  }
4166 
4170  public function setMailParticipantData(string $a_data): void
4171  {
4172  $this->mailparticipantdata = $a_data;
4173  }
4174 
4179  int $finished_id
4180  ): int {
4181  $ilDB = $this->db;
4182 
4183  $result = $ilDB->queryF(
4184  "SELECT * FROM svy_times WHERE finished_fi = %s",
4185  array('integer'),
4186  array($finished_id)
4187  );
4188  $total = 0;
4189  while ($row = $ilDB->fetchAssoc($result)) {
4190  if ($row['left_page'] > 0 && $row['entered_page'] > 0) {
4191  $total += $row['left_page'] - $row['entered_page'];
4192  }
4193  }
4194  return $total;
4195  }
4196 
4197  public function updateOrder(
4198  array $a_order
4199  ): void {
4200  if (count($this->questions) === count($a_order)) {
4201  $this->questions = array_flip($a_order);
4202  $this->saveQuestionsToDb();
4203  }
4204  }
4205 
4206  public function getPoolUsage(): bool
4207  {
4208  return $this->pool_usage;
4209  }
4210 
4211  public function setPoolUsage(bool $a_value): void
4212  {
4213  $this->pool_usage = $a_value;
4214  }
4215 
4219  public function updateCode(
4220  int $a_id,
4221  string $a_email,
4222  string $a_last_name,
4223  string $a_first_name,
4224  int $a_sent
4225  ): bool {
4226  $ilDB = $this->db;
4227 
4228  $a_email = trim($a_email);
4229 
4230  // :TODO:
4231  if (($a_email && !ilUtil::is_email($a_email)) || $a_email === "") {
4232  return false;
4233  }
4234 
4235  $data = array("email" => $a_email,
4236  "lastname" => trim($a_last_name),
4237  "firstname" => trim($a_first_name));
4238 
4239  $fields = array(
4240  "externaldata" => array("text", serialize($data)),
4241  "sent" => array("integer", $a_sent)
4242  );
4243 
4244  $ilDB->update(
4245  "svy_anonymous",
4246  $fields,
4247  array("anonymous_id" => array("integer", $a_id))
4248  );
4249 
4250  return true;
4251  }
4252 
4253 
4254  //
4255  // 360°
4256  //
4257 
4258  public function get360Mode(): bool
4259  {
4260  if ($this->getMode() === self::MODE_360) {
4261  return true;
4262  }
4263  return false;
4264  }
4265 
4266  public function set360SelfEvaluation(bool $a_value): void
4267  {
4268  $this->mode_360_self_eval = $a_value;
4269  }
4270 
4271  public function get360SelfEvaluation(): bool
4272  {
4274  }
4275 
4276  public function set360SelfAppraisee(bool $a_value): void
4277  {
4278  $this->mode_360_self_appr = $a_value;
4279  }
4280 
4281  public function get360SelfAppraisee(): bool
4282  {
4284  }
4285 
4286  public function set360SelfRaters(bool $a_value): void
4287  {
4288  $this->mode_360_self_rate = $a_value;
4289  }
4290 
4291  public function get360SelfRaters(): bool
4292  {
4294  }
4295 
4296  public function set360Results(int $a_value): void
4297  {
4298  $this->mode_360_results = $a_value;
4299  }
4300 
4301  public function get360Results(): int
4302  {
4303  return $this->mode_360_results;
4304  }
4305 
4309  public function addAppraisee(
4310  int $a_user_id
4311  ): void {
4312  global $DIC;
4313 
4314  $ilDB = $DIC->database();
4315  $access = $DIC->access();
4316 
4317  if (!$this->isAppraisee($a_user_id) &&
4318  $a_user_id !== ANONYMOUS_USER_ID) {
4319  $fields = array(
4320  "obj_id" => array("integer", $this->getSurveyId()),
4321  "user_id" => array("integer", $a_user_id)
4322  );
4323  $ilDB->insert("svy_360_appr", $fields);
4324 
4325  // send notification and add to desktop
4326  if ($access->checkAccessOfUser($a_user_id, "read", "", $this->getRefId())) {
4327  $this->sendAppraiseeNotification($a_user_id);
4328  }
4329  }
4330  }
4331 
4335  public function sendAppraiseeNotification(
4336  int $a_user_id
4337  ): void {
4338  $ntf = new ilSystemNotification();
4339  $ntf->setLangModules(array("svy", "survey"));
4340  $ntf->setRefId($this->getRefId());
4341  $ntf->setGotoLangId('url');
4342 
4343  // user specific language
4344  $lng = $ntf->getUserLanguage($a_user_id);
4345 
4346  $ntf->setIntroductionLangId("svy_user_added_appraisee_mail");
4347  $subject = str_replace("%1", $this->getTitle(), $lng->txt("svy_user_added_appraisee"));
4348 
4349  // #10044
4350  $mail = new ilMail(ANONYMOUS_USER_ID);
4351  $mail->enqueue(
4352  ilObjUser::_lookupLogin($a_user_id),
4353  "",
4354  "",
4355  $subject,
4356  $ntf->composeAndGetMessage($a_user_id, null, "read", true),
4357  []
4358  );
4359  }
4360 
4365  int $a_user_id
4366  ): void {
4367  $ntf = new ilSystemNotification();
4368  $ntf->setLangModules(array("svy", "survey"));
4369  $ntf->setRefId($this->getRefId());
4370  $ntf->setGotoLangId('url');
4371 
4372  // user specific language
4373  $lng = $ntf->getUserLanguage($a_user_id);
4374 
4375  $ntf->setIntroductionLangId("svy_user_added_appraisee_close_mail");
4376  $subject = str_replace("%1", $this->getTitle(), $lng->txt("svy_user_added_appraisee"));
4377 
4378  // #10044
4379  $mail = new ilMail(ANONYMOUS_USER_ID);
4380  $mail->enqueue(
4381  ilObjUser::_lookupLogin($a_user_id),
4382  "",
4383  "",
4384  $subject,
4385  $ntf->composeAndGetMessage($a_user_id, null, "read", true),
4386  []
4387  );
4388  }
4389 
4393  public function sendRaterNotification(
4394  int $a_user_id,
4395  int $a_appraisee_id
4396  ): void {
4397  $ntf = new ilSystemNotification();
4398  $ntf->setLangModules(array("svy", "survey"));
4399  $ntf->setRefId($this->getRefId());
4400  $ntf->setGotoLangId('url');
4401 
4402  // user specific language
4403  $lng = $ntf->getUserLanguage($a_user_id);
4404 
4405  $ntf->setIntroductionLangId("svy_user_added_rater_mail");
4406  $subject = str_replace("%1", $this->getTitle(), $lng->txt("svy_user_added_rater"));
4407  $ntf->addAdditionalInfo("survey_360_appraisee", ilUserUtil::getNamePresentation($a_appraisee_id, false, false, "", true));
4408 
4409  // #10044
4410  $mail = new ilMail(ANONYMOUS_USER_ID);
4411  $mail->enqueue(
4412  ilObjUser::_lookupLogin($a_user_id),
4413  "",
4414  "",
4415  $subject,
4416  $ntf->composeAndGetMessage($a_user_id, null, "read", true),
4417  []
4418  );
4419  }
4420 
4424  public function isAppraisee(
4425  int $a_user_id
4426  ): bool {
4427  $ilDB = $this->db;
4428  $set = $ilDB->query("SELECT user_id" .
4429  " FROM svy_360_appr" .
4430  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
4431  " AND user_id = " . $ilDB->quote($a_user_id, "integer"));
4432  return (bool) $ilDB->numRows($set);
4433  }
4434 
4438  public function isAppraiseeClosed(
4439  int $a_user_id
4440  ): bool {
4441  $ilDB = $this->db;
4442 
4443  $set = $ilDB->query("SELECT has_closed" .
4444  " FROM svy_360_appr" .
4445  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
4446  " AND user_id = " . $ilDB->quote($a_user_id, "integer"));
4447  $row = $ilDB->fetchAssoc($set);
4448  return (bool) ($row["has_closed"] ?? false);
4449  }
4450 
4454  public function deleteAppraisee(
4455  int $a_user_id
4456  ): void {
4457  $ilDB = $this->db;
4458 
4459  $ilDB->manipulate("DELETE FROM svy_360_appr" .
4460  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
4461  " AND user_id = " . $ilDB->quote($a_user_id, "integer"));
4462 
4463  $set = $ilDB->query("SELECT user_id" .
4464  " FROM svy_360_rater" .
4465  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
4466  " AND appr_id = " . $ilDB->quote($a_user_id, "integer"));
4467  while ($row = $ilDB->fetchAssoc($set)) {
4468  $this->deleteRater($a_user_id, $row["user_id"]);
4469  }
4470  // appraisee will not be part of raters table
4471  if ($this->get360SelfEvaluation()) {
4472  $this->deleteRater($a_user_id, $a_user_id);
4473  }
4474  }
4475 
4479  public function getAppraiseesData(): array
4480  {
4481  $ilDB = $this->db;
4482 
4483  $res = array();
4484 
4485  $set = $ilDB->query("SELECT * FROM svy_360_appr" .
4486  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer"));
4487  while ($row = $ilDB->fetchAssoc($set)) {
4488  $name = ilObjUser::_lookupName($row["user_id"]);
4489  $name["email"] = ilObjUser::_lookupEmail($row["user_id"]);
4490  $name["name"] = $name["lastname"] . ", " . $name["firstname"];
4491  $res[$row["user_id"]] = $name;
4492 
4493  $finished = 0;
4494  $raters = $this->getRatersData($row["user_id"]);
4495  foreach ($raters as $rater) {
4496  if ($rater["finished"]) {
4497  $finished++;
4498  }
4499  }
4500  $res[$row["user_id"]]["finished"] = $finished . "/" . count($raters);
4501  $res[$row["user_id"]]["closed"] = $row["has_closed"];
4502  }
4503 
4504  return $res;
4505  }
4506 
4510  public function addRater(
4511  int $a_appraisee_id,
4512  int $a_user_id,
4513  int $a_anonymous_id = 0
4514  ): void {
4515  global $DIC;
4516 
4517  $ilDB = $DIC->database();
4518  $access = $DIC->access();
4519 
4520  if ($this->isAppraisee($a_appraisee_id) &&
4521  !$this->isRater($a_appraisee_id, $a_user_id, $a_anonymous_id)) {
4522  $fields = array(
4523  "obj_id" => array("integer", $this->getSurveyId()),
4524  "appr_id" => array("integer", $a_appraisee_id),
4525  "user_id" => array("integer", $a_user_id),
4526  "anonymous_id" => array("integer", $a_anonymous_id)
4527  );
4528  $ilDB->insert("svy_360_rater", $fields);
4529 
4530  // send notification and add to desktop
4531  if ($access->checkAccessOfUser($a_user_id, "read", "", $this->getRefId())) {
4532  // out-commented, since adding raters will end in a mail
4533  // form to send the mail "manually"
4534  // otherwise two mails would be sent (tested in individual feedback)
4535  //$this->sendRaterNotification($a_user_id, $a_appraisee_id);
4536  }
4537  }
4538  }
4539 
4543  public function isRater(
4544  int $a_appraisee_id,
4545  int $a_user_id,
4546  int $a_anonymous_id = 0
4547  ): bool {
4548  $ilDB = $this->db;
4549 
4550  // user is rater if already appraisee and active self-evaluation
4551  if ($this->isAppraisee($a_user_id) &&
4552  $this->get360SelfEvaluation() &&
4553  (!$a_appraisee_id || $a_appraisee_id === $a_user_id)) {
4554  return true;
4555  }
4556 
4557  // :TODO: should we get rid of code as well?
4558 
4559  $sql = "SELECT user_id" .
4560  " FROM svy_360_rater" .
4561  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
4562  " AND user_id = " . $ilDB->quote($a_user_id, "integer") .
4563  " AND anonymous_id = " . $ilDB->quote($a_anonymous_id, "integer");
4564  if ($a_appraisee_id) {
4565  $sql .= " AND appr_id = " . $ilDB->quote($a_appraisee_id, "integer");
4566  }
4567  $set = $ilDB->query($sql);
4568  return (bool) $ilDB->numRows($set);
4569  }
4570 
4574  public function deleteRater(
4575  int $a_appraisee_id,
4576  int $a_user_id,
4577  int $a_anonymous_id = 0
4578  ): void {
4579  $ilDB = $this->db;
4580 
4581  $finished_id = $this->getFinishedIdForAppraiseeIdAndRaterId($a_appraisee_id, $a_user_id);
4582  if ($finished_id) {
4583  $this->removeSelectedSurveyResults(array($finished_id));
4584  }
4585 
4586  $ilDB->manipulate("DELETE FROM svy_360_rater" .
4587  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
4588  " AND appr_id = " . $ilDB->quote($a_appraisee_id, "integer") .
4589  " AND user_id = " . $ilDB->quote($a_user_id, "integer") .
4590  " AND anonymous_id = " . $ilDB->quote($a_anonymous_id, "integer"));
4591  }
4592 
4596  public function getRatersData(
4597  int $a_appraisee_id
4598  ): array {
4599  $ilDB = $this->db;
4600 
4601  $res = $anonymous_ids = array();
4602 
4603  $set = $ilDB->query("SELECT * FROM svy_360_rater" .
4604  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
4605  " AND appr_id = " . $ilDB->quote($a_appraisee_id, "integer"));
4606  while ($row = $ilDB->fetchAssoc($set)) {
4607  if ($row["anonymous_id"]) {
4608  $res["a" . $row["anonymous_id"]] = array(
4609  "lastname" => "unknown code " . $row["anonymous_id"],
4610  "sent" => $row["mail_sent"],
4611  "finished" => null
4612  );
4613  $anonymous_ids[] = $row["anonymous_id"];
4614  } else {
4615  $name = ilObjUser::_lookupName($row["user_id"]);
4616  $name["name"] = $name["lastname"] . ", " . $name["firstname"];
4617  $name["user_id"] = "u" . $name["user_id"];
4618  $name["email"] = ilObjUser::_lookupEmail($row["user_id"]);
4619  $name["sent"] = $row["mail_sent"];
4620  $name["finished"] = (bool) $this->is360SurveyStarted($a_appraisee_id, (int) $row["user_id"]);
4621  $res["u" . $row["user_id"]] = $name;
4622  }
4623  }
4624 
4625  if (count($anonymous_ids)) {
4626  $data = $this->getSurveyCodesTableData($anonymous_ids);
4627  foreach ($data as $item) {
4628  if (isset($res["a" . $item["id"]])) {
4629  $res["a" . $item["id"]] = array(
4630  "user_id" => "a" . $item["id"],
4631  "lastname" => $item["last_name"],
4632  "firstname" => $item["first_name"],
4633  "name" => $item["last_name"] . ", " . $item["first_name"],
4634  "login" => "",
4635  "email" => $item["email"],
4636  "code" => $item["code"],
4637  "href" => $item["href"],
4638  "sent" => $res["a" . $item["id"]]["sent"],
4639  "finished" => (bool) $this->is360SurveyStarted($a_appraisee_id, 0, $item["code"])
4640  );
4641  }
4642  }
4643  }
4644 
4645  return $res;
4646  }
4647 
4652  public function getAppraiseesToRate(
4653  ?int $a_user_id,
4654  int $a_anonymous_id = null
4655  ): array {
4656  $ilDB = $this->db;
4657 
4658  $res = array();
4659 
4660  $sql = "SELECT appr_id FROM svy_360_rater" .
4661  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer");
4662 
4663  if ($a_user_id) {
4664  $sql .= " AND user_id = " . $ilDB->quote($a_user_id, "integer");
4665  } else {
4666  $sql .= " AND anonymous_id = " . $ilDB->quote($a_anonymous_id, "integer");
4667  }
4668 
4669  $set = $ilDB->query($sql);
4670  while ($row = $ilDB->fetchAssoc($set)) {
4671  $res[] = (int) $row["appr_id"];
4672  }
4673 
4674  // user may evaluate himself if already appraisee
4675  if ($this->get360SelfEvaluation() &&
4676  $this->isAppraisee((int) $a_user_id) &&
4677  !in_array($a_user_id, $res)) {
4678  $res[] = $a_user_id;
4679  }
4680 
4681  return $res;
4682  }
4683 
4687  public function getAnonymousIdByCode(
4688  string $a_code
4689  ): ?int {
4690  $ilDB = $this->db;
4691  $set = $ilDB->query("SELECT anonymous_id FROM svy_anonymous" .
4692  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
4693  " AND survey_key = " . $ilDB->quote($a_code, "text"));
4694  $res = $ilDB->fetchAssoc($set);
4695  return $res["anonymous_id"] ?? null;
4696  }
4697 
4701  public function is360SurveyStarted(
4702  int $appr_id,
4703  int $user_id,
4704  string $anonymous_code = null
4705  ): ?int {
4706  $ilDB = $this->db;
4707 
4708  $sql = "SELECT * FROM svy_finished" .
4709  " WHERE survey_fi =" . $ilDB->quote($this->getSurveyId(), "integer") .
4710  " AND appr_id = " . $ilDB->quote($appr_id, "integer");
4711  if ($user_id) {
4712  $sql .= " AND user_fi = " . $ilDB->quote($user_id, "integer");
4713  } else {
4714  $sql .= " AND anonymous_id = " . $ilDB->quote($anonymous_code, "text");
4715  }
4716  $result = $ilDB->query($sql);
4717  if ($result->numRows() === 0) {
4718  return null;
4719  } else {
4720  $row = $ilDB->fetchAssoc($result);
4721  return (int) $row["state"];
4722  }
4723  }
4724 
4734  string $a_code = null
4735  ): ?array {
4736  $ilUser = $this->user;
4737  $ilDB = $this->db;
4738  $user_id = $ilUser->getId();
4739  // code is obligatory?
4740  if (!$this->isAccessibleWithoutCode()) {
4741  if (!$a_code) {
4742  // registered raters do not need code
4743  if ($this->feature_config->usesAppraisees() &&
4744  $user_id !== ANONYMOUS_USER_ID &&
4745  $this->isRater(0, $user_id)) {
4746  // auto-generate code
4747  $code = $this->data_manager->code("")
4748  ->withUserId($user_id);
4749  $this->code_manager->add($code);
4750  $a_code = $this->code_manager->getByUserId($user_id);
4751  } else {
4752  return null;
4753  }
4754  }
4755  } elseif ($user_id === ANONYMOUS_USER_ID ||
4756  $this->getAnonymize() === self::ANONYMIZE_FREEACCESS) {
4757  // self::ANONYMIZE_FREEACCESS: anonymized, no codes
4758  // or anonymous user when no codes are used
4759  if (!$a_code) {
4760  // auto-generate code
4761  $code = $this->data_manager->code("")
4762  ->withUserId($user_id);
4763  $code_id = $this->code_manager->add($code);
4764  $a_code = $this->code_manager->getByCodeId($code_id);
4765  }
4766  } else {
4767  $a_code = null;
4768  }
4769 
4770  $res = array();
4771 
4772  // get runs for user id / code
4773  $sql = "SELECT * FROM svy_finished" .
4774  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer");
4775  // if proper user id is given, use it or current code
4776  if ($user_id !== ANONYMOUS_USER_ID) {
4777  $sql .= " AND (user_fi = " . $ilDB->quote($user_id, "integer") .
4778  " OR anonymous_id = " . $ilDB->quote($a_code, "text") . ")";
4779  }
4780  // use anonymous code to find finished id(s)
4781  else {
4782  $sql .= " AND anonymous_id = " . $ilDB->quote($a_code, "text");
4783  }
4784  $set = $ilDB->query($sql);
4785  while ($row = $ilDB->fetchAssoc($set)) {
4786  $res[$row["finished_id"]] = array("appr_id" => $row["appr_id"],
4787  "user_id" => $row["user_fi"],
4788  "code" => $row["anonymous_id"],
4789  "finished" => (bool) $row["state"]);
4790  }
4791  return array("code" => $a_code, "runs" => $res);
4792  }
4793 
4797  public function findCodeForUser(
4798  int $a_user_id
4799  ): string {
4800  $ilDB = $this->db;
4801 
4802  if ($a_user_id !== ANONYMOUS_USER_ID) {
4803  $set = $ilDB->query("SELECT sf.anonymous_id FROM svy_finished sf" .
4804  " WHERE sf.survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
4805  " AND sf.user_fi = " . $ilDB->quote($a_user_id, "integer"));
4806  $a_code = $ilDB->fetchAssoc($set);
4807  return (string) ($a_code["anonymous_id"] ?? "");
4808  }
4809  return "";
4810  }
4811 
4815  public function isUnusedCode(
4816  string $a_code,
4817  int $a_user_id
4818  ): bool {
4819  $ilDB = $this->db;
4820 
4821  $set = $ilDB->query("SELECT user_fi FROM svy_finished" .
4822  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
4823  " AND anonymous_id = " . $ilDB->quote($a_code, "text"));
4824  $user_id = $ilDB->fetchAssoc($set);
4825  $user_id = (int) $user_id["user_fi"];
4826 
4827  if ($user_id && ($user_id !== $a_user_id || $user_id === ANONYMOUS_USER_ID)) {
4828  return false;
4829  }
4830  return true;
4831  }
4832 
4838  int $a_appr_id,
4839  bool $a_exclude_appraisee = false
4840  ): array {
4841  $ilDB = $this->db;
4842 
4843  $res = array();
4844 
4845  $set = $ilDB->query("SELECT finished_id, user_fi FROM svy_finished" .
4846  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
4847  " AND appr_id = " . $ilDB->quote($a_appr_id, "integer"));
4848  while ($row = $ilDB->fetchAssoc($set)) {
4849  if ($a_exclude_appraisee && $row["user_fi"] == $a_appr_id) {
4850  continue;
4851  }
4852  $res[] = (int) $row["finished_id"];
4853  }
4854 
4855  return $res;
4856  }
4857 
4863  int $a_appr_id,
4864  int $a_rat_id
4865  ): ?int {
4866  $ilDB = $this->db;
4867 
4868  $set = $ilDB->query("SELECT finished_id, user_fi FROM svy_finished" .
4869  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
4870  " AND appr_id = " . $ilDB->quote($a_appr_id, "integer") .
4871  " AND user_fi = " . $ilDB->quote($a_rat_id, "integer"));
4872  if ($row = $ilDB->fetchAssoc($set)) {
4873  return (int) $row["finished_id"];
4874  }
4875  return null;
4876  }
4877 
4878 
4879  // 360° using competence/skill service
4880 
4881  public function setSkillService(bool $a_val): void
4882  {
4883  $this->mode_skill_service = $a_val;
4884  }
4885 
4886  public function getSkillService(): bool
4887  {
4889  }
4890 
4894  public function set360RaterSent(
4895  int $a_appraisee_id,
4896  int $a_user_id,
4897  int $a_anonymous_id,
4898  int $a_tstamp = null
4899  ): void {
4900  $ilDB = $this->db;
4901 
4902  if (!$a_tstamp) {
4903  $a_tstamp = time();
4904  }
4905 
4906  $ilDB->manipulate("UPDATE svy_360_rater" .
4907  " SET mail_sent = " . $ilDB->quote($a_tstamp, "integer") .
4908  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
4909  " AND appr_id = " . $ilDB->quote($a_appraisee_id, "integer") .
4910  " AND user_id = " . $ilDB->quote($a_user_id, "integer") .
4911  " AND anonymous_id = " . $ilDB->quote($a_anonymous_id, "integer"));
4912  }
4913 
4917  public function closeAppraisee(
4918  int $a_user_id
4919  ): void {
4920  global $DIC;
4921 
4922  $ilDB = $DIC->database();
4923  $user = $DIC->user();
4924 
4925  // close the appraisee
4926  $ilDB->manipulate("UPDATE svy_360_appr" .
4927  " SET has_closed = " . $ilDB->quote(time(), "integer") .
4928  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
4929  " AND user_id = " . $ilDB->quote($a_user_id, "integer"));
4930 
4931  // write competences
4932  $skmg_set = new ilSkillManagementSettings();
4933  if ($this->getSkillService() && $skmg_set->isActivated()) {
4934  $sskill = new ilSurveySkill($this);
4935  $sskill->writeAndAddAppraiseeSkills($a_user_id);
4936  }
4937 
4938  // send notification
4939  if ($user->getId() !== $a_user_id) {
4940  $this->sendAppraiseeCloseNotification($a_user_id);
4941  }
4942  }
4943 
4947  public function openAllAppraisees(): void
4948  {
4949  $ilDB = $this->db;
4950 
4951  $ilDB->manipulate("UPDATE svy_360_appr" .
4952  " SET has_closed = " . $ilDB->quote(null, "integer") .
4953  " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer"));
4954  }
4955 
4959  public static function validateExternalRaterCode(
4960  int $a_ref_id,
4961  string $a_code
4962  ): bool {
4964  global $DIC;
4965 
4966  $anonym_repo = $DIC->survey()
4967  ->internal()
4968  ->repo()
4969  ->execution()
4970  ->runSession();
4971 
4972  if (!$anonym_repo->isExternalRaterValidated($a_ref_id)) {
4973  $svy = new self($a_ref_id);
4974  $svy->loadFromDb();
4975 
4976  $domain_service = $DIC->survey()->internal()->domain();
4977  $code_manager = $domain_service->code($svy, 0);
4978  $feature_config = $domain_service->modeFeatureConfig($svy->getMode());
4979  $access_manager = $domain_service->access($a_ref_id, 0);
4980 
4981  if ($access_manager->canStartSurvey() &&
4982  $feature_config->usesAppraisees() &&
4983  $code_manager->exists($a_code)) {
4984  $anonymous_id = $svy->getAnonymousIdByCode($a_code);
4985  if ($anonymous_id) {
4986  if (count($svy->getAppraiseesToRate(null, $anonymous_id))) {
4987  $anonym_repo->setExternalRaterValidation($a_ref_id, true);
4988  return true;
4989  }
4990  }
4991  }
4992  $anonym_repo->setExternalRaterValidation($a_ref_id, false);
4993  return false;
4994  }
4995 
4996  return $anonym_repo->isExternalRaterValidated($a_ref_id);
4997  }
4998 
4999 
5000  //
5001  // reminder/notification
5002  //
5003 
5004  public function getReminderStatus(): bool
5005  {
5006  return $this->reminder_status;
5007  }
5008 
5009  public function setReminderStatus(bool $a_value): void
5010  {
5011  $this->reminder_status = $a_value;
5012  }
5013 
5014  public function getReminderStart(): ?ilDate
5015  {
5016  return $this->reminder_start;
5017  }
5018 
5019  public function setReminderStart(?ilDate $a_value = null): void
5020  {
5021  $this->reminder_start = $a_value;
5022  }
5023 
5024  public function getReminderEnd(): ?ilDate
5025  {
5026  return $this->reminder_end;
5027  }
5028 
5029  public function setReminderEnd(?ilDate $a_value = null): void
5030  {
5031  $this->reminder_end = $a_value;
5032  }
5033 
5034  public function getReminderFrequency(): int
5035  {
5037  }
5038 
5039  public function setReminderFrequency(int $a_value): void
5040  {
5041  $this->reminder_frequency = $a_value;
5042  }
5043 
5044  public function getReminderTarget(): int
5045  {
5046  return $this->reminder_target;
5047  }
5048 
5049  public function setReminderTarget(int $a_value): void
5050  {
5051  $this->reminder_target = $a_value;
5052  }
5053 
5054  public function getReminderLastSent(): ?string
5055  {
5057  }
5058 
5059  public function setReminderLastSent(?string $a_value): void
5060  {
5061  if ($a_value == "") {
5062  $a_value = null;
5063  }
5064  $this->reminder_last_sent = $a_value;
5065  }
5066 
5067  public function getReminderTemplate(
5068  bool $selectDefault = false
5069  ): ?int {
5070  if ($selectDefault) {
5071  $defaultTemplateId = 0;
5072  $this->getReminderMailTemplates($defaultTemplateId);
5073 
5074  if ($defaultTemplateId > 0) {
5075  return $defaultTemplateId;
5076  }
5077  }
5078 
5079  return $this->reminder_tmpl;
5080  }
5081 
5082  public function setReminderTemplate(?int $a_value): void
5083  {
5084  $this->reminder_tmpl = $a_value;
5085  }
5086 
5087  public function getTutorNotificationStatus(): bool
5088  {
5089  return $this->tutor_ntf_status;
5090  }
5091 
5095  public function setTutorNotificationStatus(bool $a_value): void
5096  {
5097  $this->tutor_ntf_status = $a_value;
5098  }
5099 
5103  public function getTutorNotificationRecipients(): array
5104  {
5106  }
5107 
5111  public function setTutorNotificationRecipients(array $a_value): void
5112  {
5113  $this->tutor_ntf_recipients = $a_value;
5114  }
5115 
5122  public function getTutorNotificationTarget(): int
5123  {
5124  return $this->tutor_ntf_target;
5125  }
5126 
5127  public function setTutorNotificationTarget(int $a_value): void
5128  {
5129  $this->tutor_ntf_target = $a_value;
5130  }
5131 
5132  public function getTutorResultsStatus(): bool
5133  {
5134  return $this->tutor_res_status;
5135  }
5136 
5137  public function setTutorResultsStatus(bool $a_value): void
5138  {
5139  $this->tutor_res_status = $a_value;
5140  }
5141 
5142  public function getTutorResultsRecipients(): array
5143  {
5145  }
5146 
5147  public function setTutorResultsRecipients(array $a_value): void
5148  {
5149  $this->tutor_res_recipients = $a_value;
5150  }
5151 
5156  protected function checkTutorNotification(): void
5157  {
5158  $ilDB = $this->db;
5159 
5160  if ($this->getTutorNotificationStatus()) {
5161  // get target users, either parent course/group members or
5162  // user with the survey on the dashboard
5163  $user_ids = $this->getNotificationTargetUserIds(($this->getTutorNotificationTarget() === self::NOTIFICATION_INVITED_USERS));
5164  if ($user_ids) {
5165  $set = $ilDB->query("SELECT COUNT(*) numall FROM svy_finished" .
5166  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
5167  " AND state = " . $ilDB->quote(1, "integer") .
5168  " AND " . $ilDB->in("user_fi", $user_ids, "", "integer"));
5169  $row = $ilDB->fetchAssoc($set);
5170 
5171  // all users finished the survey -> send notifications
5172  if ((int) $row["numall"] === count($user_ids)) {
5173  $this->sendTutorNotification();
5174  }
5175  }
5176  }
5177  }
5178 
5182  public function sent360Reminders(): void
5183  {
5184  global $DIC;
5185 
5186  $access = $DIC->access();
5187  // collect all open ratings
5188  $rater_ids = array();
5189  foreach ($this->getAppraiseesData() as $app) {
5190  $this->svy_log->debug("Handle appraisee " . $app['user_id']);
5191 
5192  if (!$this->isAppraiseeClosed($app['user_id'])) {
5193  $this->svy_log->debug("Check self evaluation, self: " . $this->get360SelfAppraisee() . ", target: " . $this->getReminderTarget());
5194 
5195  // self evaluation?
5196  if ($this->get360SelfEvaluation() &&
5197  in_array($this->getReminderTarget(), array(self::NOTIFICATION_APPRAISEES, self::NOTIFICATION_APPRAISEES_AND_RATERS))) {
5198  $this->svy_log->debug("...1");
5199  // did user already finished self evaluation?
5200  if (!$this->is360SurveyStarted((int) $app['user_id'], (int) $app['user_id'])) {
5201  $this->svy_log->debug("...2");
5202  if (!isset($rater_ids[$app['user_id']])) {
5203  $rater_ids[$app['user_id']] = array();
5204  }
5205  if (!isset($app["user_id"], $rater_ids[$app['user_id']])) {
5206  $rater_ids[$app['user_id']][] = $app["user_id"];
5207  }
5208  }
5209  }
5210 
5211  $this->svy_log->debug("Check raters.");
5212 
5213  // should raters be notified?
5214  if (
5215  in_array(
5216  $this->getReminderTarget(),
5217  array(self::NOTIFICATION_RATERS, self::NOTIFICATION_APPRAISEES_AND_RATERS),
5218  true
5219  )
5220  ) {
5221  foreach ($this->getRatersData($app['user_id']) as $rater) {
5222  $rater_id = 0;
5223  if ($rater["login"] !== "") {
5224  $rater_id = ilObjUser::_lookupId($rater["login"]);
5225  }
5226  if ($rater_id > 0) {
5227  // is rater not anonymous and did not rate yet?
5228  if (!($rater["anonymous_id"] ?? false) && !($rater["finished"] ?? false)) {
5229  if (!isset($rater_ids[$rater_id])) {
5230  $rater_ids[$rater_id] = array();
5231  }
5232  if (!in_array($app["user_id"], $rater_ids[$rater_id])) {
5233  $rater_ids[$rater_id][] = $app["user_id"];
5234  }
5235  }
5236  }
5237  }
5238  }
5239  }
5240  }
5241 
5242  $this->svy_log->debug("Found raters:" . count($rater_ids));
5243 
5244  foreach ($rater_ids as $id => $app) {
5245  if ($access->checkAccessOfUser((int) $id, "read", "", $this->getRefId())) {
5246  $this->send360ReminderToUser((int) $id, $app);
5247  }
5248  }
5249  }
5250 
5251  public function send360ReminderToUser(
5252  int $a_user_id,
5253  array $a_appraisee_ids
5254  ): void {
5255  $this->svy_log->debug("Send mail to:" . $a_user_id);
5256 
5257  $ntf = new ilSystemNotification();
5258  $ntf->setLangModules(array("svy", "survey"));
5259  $ntf->setRefId($this->getRefId());
5260  $ntf->setGotoLangId('url');
5261 
5262  // user specific language
5263  $lng = $ntf->getUserLanguage($a_user_id);
5264 
5265  $ntf->setIntroductionLangId("svy_user_added_rater_reminder_mail");
5266  $subject = str_replace("%1", $this->getTitle(), $lng->txt("svy_user_added_rater"));
5267 
5268  foreach ($a_appraisee_ids as $appraisee_id) {
5269  $ntf->addAdditionalInfo("survey_360_appraisee", ilUserUtil::getNamePresentation($appraisee_id, false, false, "", true));
5270  }
5271 
5272  // #10044
5273  $mail = new ilMail(ANONYMOUS_USER_ID);
5274  $mail->enqueue(
5275  ilObjUser::_lookupLogin($a_user_id),
5276  "",
5277  "",
5278  $subject,
5279  $ntf->composeAndGetMessage($a_user_id, null, "read", true),
5280  []
5281  );
5282  }
5283 
5292  bool $a_use_invited
5293  ): array {
5294  $tree = $this->tree;
5295 
5296  $user_ids = [];
5297  if ($a_use_invited) {
5298  $user_ids = $this->invitation_manager->getAllForSurvey($this->getSurveyId());
5299  } else {
5300  $parent_grp_ref_id = $tree->checkForParentType($this->getRefId(), "grp");
5301  if ($parent_grp_ref_id) {
5302  $part = new ilGroupParticipants(ilObject::_lookupObjId($parent_grp_ref_id));
5303  $user_ids = $part->getMembers();
5304  } else {
5305  $parent_crs_ref_id = $tree->checkForParentType($this->getRefId(), "crs");
5306  if ($parent_crs_ref_id) {
5307  $part = new ilCourseParticipants(ilObject::_lookupObjId($parent_crs_ref_id));
5308  $user_ids = $part->getMembers();
5309  }
5310  }
5311  }
5312  return $user_ids;
5313  }
5314 
5318  protected function sendTutorNotification(): void
5319  {
5320  $link = ilLink::_getStaticLink($this->getRefId(), "svy");
5321 
5322  // get tutors being set in the setting
5323  foreach ($this->getTutorNotificationRecipients() as $user_id) {
5324  // use language of recipient to compose message
5325  $ulng = ilLanguageFactory::_getLanguageOfUser($user_id);
5326  $ulng->loadLanguageModule('survey');
5327 
5328  $subject = sprintf($ulng->txt('survey_notification_tutor_subject'), $this->getTitle());
5329  $message = sprintf($ulng->txt('survey_notification_tutor_salutation'), ilObjUser::_lookupFullname($user_id)) . "\n\n";
5330 
5331  $message .= $ulng->txt('survey_notification_tutor_body') . ":\n\n";
5332  $message .= $ulng->txt('obj_svy') . ": " . $this->getTitle() . "\n";
5333  $message .= "\n" . $ulng->txt('survey_notification_tutor_link') . ": " . $link;
5334 
5335  $mail_obj = new ilMail(ANONYMOUS_USER_ID);
5336  $mail_obj->appendInstallationSignature(true);
5337  $mail_obj->enqueue(
5338  ilObjUser::_lookupLogin($user_id),
5339  "",
5340  "",
5341  $subject,
5342  $message,
5343  array()
5344  );
5345  }
5346  }
5347 
5348  public function checkReminder(): ?int
5349  {
5350  $ilDB = $this->db;
5351  $ilAccess = $this->access;
5352 
5353  $now = time();
5354  $now_with_format = date("YmdHis", $now);
5355  $today = date("Y-m-d");
5356 
5357  $this->svy_log->debug("Check status and dates.");
5358 
5359  // object settings / participation period
5360  if (
5361  $this->getOfflineStatus() ||
5362  !$this->getReminderStatus() ||
5363  ($this->getStartDate() && $now_with_format < $this->getStartDate()) ||
5364  ($this->getEndDate() && $now_with_format > $this->getEndDate())) {
5365  return null;
5366  }
5367 
5368  // reminder period
5369  $start = $this->getReminderStart();
5370  if ($start) {
5371  $start = $start->get(IL_CAL_DATE);
5372  }
5373  $end = $this->getReminderEnd();
5374  if ($end) {
5375  $end = $end->get(IL_CAL_DATE);
5376  }
5377  if ($today < $start ||
5378  ($end && $today > $end)) {
5379  return null;
5380  }
5381 
5382  $this->svy_log->debug("Check access period.");
5383 
5384  // object access period
5385  $item_data = ilObjectActivation::getItem($this->getRefId());
5386  if ((int) $item_data["timing_type"] === ilObjectActivation::TIMINGS_ACTIVATION &&
5387  ($now < $item_data["timing_start"] ||
5388  $now > $item_data["timing_end"])) {
5389  return null;
5390  }
5391 
5392  $this->svy_log->debug("Check frequency.");
5393 
5394  // check frequency
5395  $cut = new ilDate($today, IL_CAL_DATE);
5396  $cut->increment(IL_CAL_DAY, $this->getReminderFrequency() * -1);
5397  if (!$this->getReminderLastSent() ||
5398  $cut->get(IL_CAL_DATE) >= substr($this->getReminderLastSent(), 0, 10)) {
5399  $missing_ids = array();
5400  if (!$this->feature_config->usesAppraisees()) {
5401  $this->svy_log->debug("Entering survey mode.");
5402 
5403  // #16871
5404  $user_ids = $this->getNotificationTargetUserIds(($this->getReminderTarget() === self::NOTIFICATION_INVITED_USERS));
5405  if ($user_ids) {
5406  // gather participants who already finished
5407  $finished_ids = array();
5408  $set = $ilDB->query("SELECT user_fi FROM svy_finished" .
5409  " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
5410  " AND state = " . $ilDB->quote(1, "text") .
5411  " AND " . $ilDB->in("user_fi", $user_ids, "", "integer"));
5412  while ($row = $ilDB->fetchAssoc($set)) {
5413  $finished_ids[] = $row["user_fi"];
5414  }
5415 
5416  // some users missing out?
5417  $missing_ids = array_diff($user_ids, $finished_ids);
5418  if ($missing_ids) {
5419  foreach ($missing_ids as $idx => $user_id) {
5420  // should be able to participate
5421  if (!$ilAccess->checkAccessOfUser($user_id, "read", "", $this->getRefId(), "svy", $this->getId())) {
5422  unset($missing_ids[$idx]);
5423  }
5424  }
5425  }
5426  if ($missing_ids) {
5427  $this->sentReminder($missing_ids);
5428  }
5429  }
5430  } else {
5431  $this->svy_log->debug("Entering 360 mode.");
5432 
5433  $this->sent360Reminders();
5434  }
5435 
5436 
5437  $this->setReminderLastSent($today);
5438  $this->saveToDb();
5439 
5440  return count($missing_ids);
5441  }
5442 
5443  return null;
5444  }
5445 
5446  protected function sentReminder(
5447  array $a_recipient_ids
5448  ): void {
5449  global $DIC;
5450 
5451  $link = "";
5452 
5453  // use mail template
5454  if ($this->getReminderTemplate() &&
5455  array_key_exists($this->getReminderTemplate(), $this->getReminderMailTemplates())) {
5457  $templateService = $DIC->mail()->textTemplates();
5458  $tmpl = $templateService->loadTemplateForId($this->getReminderTemplate());
5459 
5460  $tmpl_params = array(
5461  "ref_id" => $this->getRefId(),
5462  "ts" => time()
5463  );
5464  } else {
5465  $tmpl = null;
5466  $tmpl_params = null;
5467  $link = ilLink::_getStaticLink($this->getRefId(), "svy");
5468  }
5469 
5470  foreach ($a_recipient_ids as $user_id) {
5471  if ($tmpl) {
5472  $subject = $tmpl->getSubject();
5473  $message = $this->sentReminderPlaceholders($tmpl->getMessage(), $user_id, $tmpl_params);
5474  }
5475  // use lng
5476  else {
5477  // use language of recipient to compose message
5478  $ulng = ilLanguageFactory::_getLanguageOfUser($user_id);
5479  $ulng->loadLanguageModule('survey');
5480 
5481  $subject = sprintf($ulng->txt('survey_reminder_subject'), $this->getTitle());
5482  $message = sprintf($ulng->txt('survey_reminder_salutation'), ilObjUser::_lookupFullname($user_id)) . "\n\n";
5483 
5484  $message .= $ulng->txt('survey_reminder_body') . ":\n\n";
5485  $message .= $ulng->txt('obj_svy') . ": " . $this->getTitle() . "\n";
5486  $message .= "\n" . $ulng->txt('survey_reminder_link') . ": " . $link;
5487  }
5488 
5489  $mail_obj = new ilMail(ANONYMOUS_USER_ID);
5490  $mail_obj->appendInstallationSignature(true);
5491  $mail_obj->enqueue(
5492  ilObjUser::_lookupLogin($user_id),
5493  "",
5494  "",
5495  $subject,
5496  $message,
5497  array()
5498  );
5499  }
5500  }
5501 
5502  public function setActivationStartDate(
5503  int $starting_time = null
5504  ): void {
5505  $this->activation_starting_time = $starting_time;
5506  }
5507 
5508  public function setActivationEndDate(
5509  int $ending_time = null
5510  ): void {
5511  $this->activation_ending_time = $ending_time;
5512  }
5513 
5514  public function getActivationStartDate(): ?int
5515  {
5517  }
5518 
5519  public function getActivationEndDate(): ?int
5520  {
5522  }
5523 
5524  public function setViewOwnResults(bool $a_value): void
5525  {
5526  $this->view_own_results = $a_value;
5527  }
5528 
5529  public function hasViewOwnResults(): bool
5530  {
5531  return $this->view_own_results;
5532  }
5533 
5534  public function setMailOwnResults(bool $a_value): void
5535  {
5536  $this->mail_own_results = $a_value;
5537  }
5538 
5539  public function hasMailOwnResults(): bool
5540  {
5541  return $this->mail_own_results;
5542  }
5543 
5544  public function setMailConfirmation(bool $a_value): void
5545  {
5546  $this->mail_confirmation = $a_value;
5547  }
5548 
5549  public function hasMailConfirmation(): bool
5550  {
5551  return $this->mail_confirmation;
5552  }
5553 
5554  public function setAnonymousUserList(bool $a_value): void
5555  {
5556  $this->anon_user_list = $a_value;
5557  }
5558 
5559  public function hasAnonymousUserList(): bool
5560  {
5561  return $this->anon_user_list;
5562  }
5563 
5564  public static function getSurveySkippedValue(): string
5565  {
5566  global $DIC;
5567 
5568  $lng = $DIC->language();
5569 
5570  // #13541
5571 
5572  $surveySetting = new ilSetting("survey");
5573  if (!$surveySetting->get("skipped_is_custom", false)) {
5574  return $lng->txt("skipped");
5575  } else {
5576  return $surveySetting->get("skipped_custom_value", "");
5577  }
5578  }
5579 
5580  public function getReminderMailTemplates(
5581  int &$defaultTemplateId = null
5582  ): array {
5583  global $DIC;
5584 
5585  $res = array();
5586 
5587  $templateService = $DIC->mail()->textTemplates();
5588  foreach ($templateService->loadTemplatesForContextId(ilSurveyMailTemplateReminderContext::ID) as $tmpl) {
5589  $res[$tmpl->getTplId()] = $tmpl->getTitle();
5590  if (null !== $defaultTemplateId && $tmpl->isDefault()) {
5591  $defaultTemplateId = $tmpl->getTplId();
5592  }
5593  }
5594 
5595  return $res;
5596  }
5597 
5598  protected function sentReminderPlaceholders(
5599  string $a_message,
5600  int $a_user_id,
5601  array $a_context_params
5602  ): string {
5603  // see ilMail::replacePlaceholders()
5604  try {
5606 
5607  $user = new \ilObjUser($a_user_id);
5608 
5609  $a_message = $this->placeholder_resolver->resolve($context, $a_message, $user, $a_context_params);
5610  } catch (\Exception $e) {
5611  ilLoggerFactory::getLogger('mail')->error(__METHOD__ . ' has been called with invalid context.');
5612  }
5613 
5614  return $a_message;
5615  }
5616 
5617  public function setMode(int $a_value): void
5618  {
5619  $this->mode = $a_value;
5620  }
5621 
5622  public function getMode(): int
5623  {
5624  return $this->mode;
5625  }
5626 
5627  public function setSelfEvaluationResults(int $a_value): void
5628  {
5629  $this->mode_self_eval_results = $a_value;
5630  }
5631 
5632  public function getSelfEvaluationResults(): int
5633  {
5635  }
5636 
5640  public static function getSurveysWithTutorResults(): array
5641  {
5642  global $ilDB;
5643 
5644  $res = array();
5645 
5647 
5648 
5649  $q = "SELECT obj_fi FROM svy_svy" .
5650  " WHERE tutor_res_cron IS NULL" .
5651  " AND tutor_res_status = " . $ilDB->quote(1, "integer") .
5652  " AND enddate < " . $ilDB->quote(date("Ymd000000"), "text");
5653 
5654  if (DEVMODE) {
5655  $q = "SELECT obj_fi FROM svy_svy" .
5656  " WHERE tutor_res_status = " . $ilDB->quote(1, "integer") .
5657  " AND enddate < " . $ilDB->quote(date("Ymd000000"), "text");
5658  }
5659 
5660  $set = $ilDB->query($q);
5661 
5662  $log->debug($q);
5663 
5664  while ($row = $ilDB->fetchAssoc($set)) {
5665  $res[] = (int) $row["obj_fi"];
5666  }
5667 
5668  return $res;
5669  }
5670 
5671  public function getMaxSumScore(): int
5672  {
5673  $sum_score = 0;
5675  $sum_score += call_user_func([$c, "getMaxSumScore"], $this->getSurveyId());
5676  }
5677  return $sum_score;
5678  }
5679 }
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)
$app
Definition: cli.php:39
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)
getSurveyCodesTableData(array $ids=null, string $lang=null)
Fetches the data for the survey codes table.
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:69
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.
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.
$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...
static is_email(string $a_email, ilMailRfc822AddressParserFactory $mailAddressParserFactory=null)
This preg-based function checks whether an e-mail address is formally valid.
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)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
const EVALUATION_ACCESS_PARTICIPANTS
static _lookupFullname(int $a_user_id)
getVariables(int $question_id)
Returns all variables (answer options/scale values) of a question.
setReminderTemplate(?int $a_value)
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:33
static getSurveySkippedValue()
string $author
A text representation of the authors name.
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...
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
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
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)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
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.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
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.
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)
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...
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)
setActivationEndDate(int $ending_time=null)
sendTutorNotification()
Send mail to tutors after all participants have finished the survey.
sentReminderPlaceholders(string $a_message, int $a_user_id, array $a_context_params)
setTutorNotificationStatus(bool $a_value)
Activates mail being sent to tutors after all participants have finished the survey.
static _lookupObjId(int $ref_id)
static _instanciateQuestionEvaluation(int $question_id, array $a_finished_ids=null)
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)
setOutro(string $outro="")
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
global $DIC
Definition: feed.php:28
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)
__construct(VocabulariesInterface $vocabularies)
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)
getAppraiseesToRate(?int $a_user_id, int $a_anonymous_id=null)
isQuestionInSurvey(int $a_question_fi)
string $evaluation_access
ilAccessHandler $access
sendAppraiseeNotification(int $a_user_id)
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)
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)
setStartDate(string $start_date="")
string $key
Consumer key/client ID value.
Definition: System.php:193
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.
$url
Definition: ltiregstart.php:35
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" ...
set360RaterSent(int $a_appraisee_id, int $a_user_id, int $a_anonymous_id, int $a_tstamp=null)
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.
is360SurveyStarted(int $appr_id, int $user_id, string $anonymous_code=null)
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:26
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)
setActivationStartDate(int $starting_time=null)
isAppraiseeClosed(int $a_user_id)
ilErrorHandling $error
setActivationLimited(bool $a_value)
addRater(int $a_appraisee_id, int $a_user_id, int $a_anonymous_id=0)
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
$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:32
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...
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...
getReminderMailTemplates(int &$defaultTemplateId=null)
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.
getUserSurveyExecutionStatus(string $a_code=null)
Get current user execution status.
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)
static _lookupLogin(int $a_user_id)