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