1<?php
2
3/* Copyright (c) 1998-2010 ILIAS open source, Extended GPL, see docs/LICENSE */
4
5use \ILIAS\Survey\Participants;
6
7/**
8 * Class ilObjSurvey
9 *
10 * @author		Helmut Schottmüller <helmut.schottmueller@mac.com>
11 */
12class ilObjSurvey extends ilObject
13{
14    /**
15     * @var ilObjUser
16     */
17    protected $user;
18
19    /**
20     * @var ilAccessHandler
21     */
22    protected $access;
23
24    /**
25     * @var ilPluginAdmin
26     */
27    protected $plugin_admin;
28
29    const EVALUATION_ACCESS_OFF = 0;
30    const EVALUATION_ACCESS_ALL = 1;
31    const EVALUATION_ACCESS_PARTICIPANTS = 2;
32
33
34    const ANONYMIZE_OFF = 0; // personalized, no codes
35    const ANONYMIZE_ON = 1; // anonymized, codes
36    const ANONYMIZE_FREEACCESS = 2; // anonymized, no codes
37    const ANONYMIZE_CODE_ALL = 3; // personalized, codes
38
39    const QUESTIONTITLES_HIDDEN = 0;
40    const QUESTIONTITLES_VISIBLE = 1;
41
42    // constants to define the print view values.
43    const PRINT_HIDE_LABELS = 1; // Show only the titles in "print" and "PDF Export"
44    const PRINT_SHOW_LABELS = 3; // Show titles and labels in "print" and "PDF Export"
45
46    /**
47    * A unique positive numerical ID which identifies the survey.
48    * This is the primary key from a database table.
49    *
50    * @var integer
51    */
52    public $survey_id;
53
54    /**
55    * A text representation of the authors name. The name of the author must
56    * not necessary be the name of the owner.
57    *
58    * @var string
59    */
60    public $author;
61
62    /**
63    * A text representation of the surveys introduction.
64    *
65    * @var string
66    */
67    public $introduction;
68
69    /**
70    * A text representation of the surveys outro.
71    *
72    * @var string
73    */
74    public $outro;
75
76
77    /**
78    * Indicates the evaluation access for learners
79    *
80    * @var string
81    */
82    public $evaluation_access;
83
84    /**
85    * The start date of the survey
86    *
87    * @var string
88    */
89    public $start_date;
90
91    /**
92    * The end date of the survey
93    *
94    * @var string
95    */
96    public $end_date;
97
98    /**
99    * The questions contained in this survey
100    *
101    * @var array
102    */
103    public $questions;
104
105
106    /**
107    * Indicates the anonymization of the survey
108    * @var integer
109    */
110    public $anonymize;
111
112    /**
113    * Indicates if the question titles are shown during a query
114    * @var integer
115    */
116    public $display_question_titles;
117
118    /**
119     * Indicates if a survey code may be exported with the survey statistics
120     *
121     * @var boolean
122     **/
123    public $surveyCodeSecurity;
124
125    public $mailnotification;
126    public $mailaddresses;
127    public $mailparticipantdata;
128    public $template_id;
129    public $pool_usage;
130
131    /**
132     * @var ilLogger
133     */
134    protected $log;
135
136    protected $activation_visibility;
137    protected $activation_starting_time;
138    protected $activation_ending_time;
139
140    // 360°
141    protected $mode_360_self_eval; // [bool]
142    protected $mode_360_self_appr; // [bool]
143    protected $mode_360_self_rate; // [bool]
144    protected $mode_360_results; // [int]
145    protected $mode_skill_service; // [bool]
146
147    const RESULTS_360_NONE = 0;
148    const RESULTS_360_OWN = 1;
149    const RESULTS_360_ALL = 2;
150
151    // reminder/notification
152    protected $reminder_status; // [bool]
153    protected $reminder_start; // [ilDate]
154    protected $reminder_end; // [ilDate]
155    protected $reminder_frequency; // [int]
156    protected $reminder_target; // [int]
157    protected $reminder_last_sent; // [bool]
158    protected $reminder_tmpl; // [int]
159    protected $tutor_ntf_status; // [bool]
160    protected $tutor_ntf_recipients; // [array]
161    protected $tutor_ntf_target; // [int]
162    protected $tutor_res_status; // [bool]
163    protected $tutor_res_recipients; // [array]
164
165    protected $view_own_results; // [bool]
166    protected $mail_own_results; // [bool]
167    protected $mail_confirmation; // [bool]
168
169    protected $anon_user_list; // [bool]
170
171    const NOTIFICATION_PARENT_COURSE = 1;
172    const NOTIFICATION_INVITED_USERS = 2;
173    const NOTIFICATION_APPRAISEES = 3;
174    const NOTIFICATION_RATERS = 4;
175    const NOTIFICATION_APPRAISEES_AND_RATERS = 5;
176
177    protected $mode; //[int]
178    protected $mode_self_eval_results; //[int]
179
180    //MODE TYPES
181    const MODE_STANDARD = 0;
182    const MODE_360 = 1;
183    const MODE_SELF_EVAL = 2;
184
185    //self evaluation only access to results
186    const RESULTS_SELF_EVAL_NONE = 0;
187    const RESULTS_SELF_EVAL_OWN = 1;
188    const RESULTS_SELF_EVAL_ALL = 2;
189
190
191    /**
192     * @var Participants\InvitationsManager
193     */
194    protected $invitation_manager;
195
196    /**
197    * Constructor
198    * @access	public
199    * @param	integer	reference_id or object_id
200    * @param	boolean	treat the id as reference_id (true) or object_id (false)
201    */
202    public function __construct($a_id = 0, $a_call_by_reference = true)
203    {
204        global $DIC;
205
206        $this->user = $DIC->user();
207        $this->lng = $DIC->language();
208        $this->db = $DIC->database();
209        $this->access = $DIC->access();
210        $this->log = $DIC["ilLog"];
211        $this->plugin_admin = $DIC["ilPluginAdmin"];
212        $this->tree = $DIC->repositoryTree();
213        $ilUser = $DIC->user();
214        $lng = $DIC->language();
215
216        $this->type = "svy";
217        $this->survey_id = -1;
218        $this->introduction = "";
219        $this->outro = $lng->txt("survey_finished");
220        $this->author = $ilUser->getFullname();
221        $this->evaluation_access = self::EVALUATION_ACCESS_OFF;
222        $this->questions = array();
223        $this->anonymize = self::ANONYMIZE_OFF;
224        $this->display_question_titles = self::QUESTIONTITLES_VISIBLE;
225        $this->surveyCodeSecurity = true;
226        $this->template_id = null;
227        $this->pool_usage = true;
228        $this->log = ilLoggerFactory::getLogger("svy");
229        $this->mode = self::MODE_STANDARD;
230        $this->mode_self_eval_results = self::RESULTS_SELF_EVAL_OWN;
231
232        $this->invitation_manager = new Participants\InvitationsManager();
233
234        parent::__construct($a_id, $a_call_by_reference);
235    }
236
237    /**
238    * create survey object
239    */
240    public function create($a_upload = false)
241    {
242        parent::create();
243        if (!$a_upload) {
244            $this->createMetaData();
245        }
246        $this->setOfflineStatus(true);
247        $this->update($a_upload);
248    }
249
250    /**
251    * Create meta data entry
252    *
253    * @access public
254    */
255    public function createMetaData()
256    {
257        parent::createMetaData();
258        $this->saveAuthorToMetadata();
259    }
260
261    /**
262    * update object data
263    *
264    * @access	public
265    * @return	boolean
266    */
267    public function update($a_upload = false)
268    {
269        if (!$a_upload) {
270            $this->updateMetaData();
271        }
272
273        if (!parent::update()) {
274            return false;
275        }
276
277        // put here object specific stuff
278
279        return true;
280    }
281
282    public function createReference()
283    {
284        $result = parent::createReference();
285        $this->saveToDb();
286        return $result;
287    }
288
289    /**
290     * read object data from db into object
291     * @access	public
292     */
293    public function read()
294    {
295        parent::read();
296        $this->loadFromDb();
297    }
298
299    /**
300    * Adds a question to the survey (used in importer!)
301    *
302    * @param	integer	$question_id The question id of the question
303    * @access	public
304    */
305    public function addQuestion($question_id)
306    {
307        array_push($this->questions, $question_id);
308    }
309
310    /**
311    * delete object and all related data
312    *
313    * @access	public
314    * @return	boolean	true if all object data were removed; false if only a references were removed
315    */
316    public function delete()
317    {
318        if ($this->countReferences() == 1) {
319            $this->deleteMetaData();
320
321            // Delete all survey questions, constraints and materials
322            foreach ($this->questions as $question_id) {
323                $this->removeQuestion($question_id);
324            }
325            $this->deleteSurveyRecord();
326
327            ilUtil::delDir($this->getImportDirectory());
328        }
329
330        $remove = parent::delete();
331
332        // always call parent delete function first!!
333        if (!$remove) {
334            return false;
335        }
336        return true;
337    }
338
339    /**
340    * Deletes the survey from the database
341    *
342    * @access	public
343    */
344    public function deleteSurveyRecord()
345    {
346        $ilDB = $this->db;
347
348        $affectedRows = $ilDB->manipulateF(
349            "DELETE FROM svy_svy WHERE survey_id = %s",
350            array('integer'),
351            array($this->getSurveyId())
352        );
353
354        $result = $ilDB->queryF(
355            "SELECT questionblock_fi FROM svy_qblk_qst WHERE survey_fi = %s",
356            array('integer'),
357            array($this->getSurveyId())
358        );
359        $questionblocks = array();
360        while ($row = $ilDB->fetchAssoc($result)) {
361            array_push($questionblocks, $row["questionblock_fi"]);
362        }
363        if (count($questionblocks)) {
364            $affectedRows = $ilDB->manipulate("DELETE FROM svy_qblk WHERE " . $ilDB->in('questionblock_id', $questionblocks, false, 'integer'));
365        }
366        $affectedRows = $ilDB->manipulateF(
367            "DELETE FROM svy_qblk_qst WHERE survey_fi = %s",
368            array('integer'),
369            array($this->getSurveyId())
370        );
371        $this->deleteAllUserData(false);
372
373        $affectedRows = $ilDB->manipulateF(
374            "DELETE FROM svy_anonymous WHERE survey_fi = %s",
375            array('integer'),
376            array($this->getSurveyId())
377        );
378
379        // delete export files
380        $svy_data_dir = ilUtil::getDataDir() . "/svy_data";
381        $directory = $svy_data_dir . "/svy_" . $this->getId();
382        if (is_dir($directory)) {
383            ilUtil::delDir($directory);
384        }
385
386        $mobs = ilObjMediaObject::_getMobsOfObject("svy:html", $this->getId());
387        // remaining usages are not in text anymore -> delete them
388        // and media objects (note: delete method of ilObjMediaObject
389        // checks whether object is used in another context; if yes,
390        // the object is not deleted!)
391        foreach ($mobs as $mob) {
392            ilObjMediaObject::_removeUsage($mob, "svy:html", $this->getId());
393            $mob_obj = new ilObjMediaObject($mob);
394            $mob_obj->delete();
395        }
396    }
397
398    /**
399     * Deletes all user data of a survey
400     *
401     * @access    public
402     * @param bool $reset_LP	notice that the LP can only be reset it the determining components still exist
403     */
404    public function deleteAllUserData($reset_LP = true)
405    {
406        $ilDB = $this->db;
407
408        $result = $ilDB->queryF(
409            "SELECT finished_id FROM svy_finished WHERE survey_fi = %s",
410            array('integer'),
411            array($this->getSurveyId())
412        );
413        $active_array = array();
414        while ($row = $ilDB->fetchAssoc($result)) {
415            array_push($active_array, $row["finished_id"]);
416        }
417
418        $affectedRows = $ilDB->manipulateF(
419            "DELETE FROM svy_finished WHERE survey_fi = %s",
420            array('integer'),
421            array($this->getSurveyId())
422        );
423
424        foreach ($active_array as $active_fi) {
425            $affectedRows = $ilDB->manipulateF(
426                "DELETE FROM svy_answer WHERE active_fi = %s",
427                array('integer'),
428                array($active_fi)
429            );
430            $affectedRows = $ilDB->manipulateF(
431                "DELETE FROM svy_times WHERE finished_fi = %s",
432                array('integer'),
433                array($active_fi)
434            );
435        }
436
437        if ($reset_LP) {
438            $lp_obj = ilObjectLP::getInstance($this->getId());
439            $lp_obj->resetLPDataForCompleteObject();
440        }
441    }
442
443    /**
444    * Deletes the user data of a given array of survey participants
445    *
446    * @access	public
447    */
448    public function removeSelectedSurveyResults($finished_ids)
449    {
450        $ilDB = $this->db;
451
452        $user_ids[] = array();
453
454        foreach ($finished_ids as $finished_id) {
455            $result = $ilDB->queryF(
456                "SELECT finished_id FROM svy_finished WHERE finished_id = %s",
457                array('integer'),
458                array($finished_id)
459            );
460            $row = $ilDB->fetchAssoc($result);
461
462            if ($row["user_fi"]) {
463                $user_ids[] = $row["user_fi"];
464            }
465
466            $affectedRows = $ilDB->manipulateF(
467                "DELETE FROM svy_answer WHERE active_fi = %s",
468                array('integer'),
469                array($row["finished_id"])
470            );
471
472            $affectedRows = $ilDB->manipulateF(
473                "DELETE FROM svy_finished WHERE finished_id = %s",
474                array('integer'),
475                array($finished_id)
476            );
477
478            $affectedRows = $ilDB->manipulateF(
479                "DELETE FROM svy_times WHERE finished_fi = %s",
480                array('integer'),
481                array($row["finished_id"])
482            );
483        }
484
485        if (sizeof($user_ids)) {
486            $lp_obj = ilObjectLP::getInstance($this->getId());
487            $lp_obj->resetLPDataForUserIds($user_ids);
488        }
489    }
490
491    public function &getSurveyParticipants($finished_ids = null, $force_non_anonymous = false, $include_invites = false)
492    {
493        $ilDB = $this->db;
494
495        $sql = "SELECT * FROM svy_finished" .
496            " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer");
497        if ($finished_ids) {
498            $sql .= " AND " . $ilDB->in("finished_id", $finished_ids, "", "integer");
499        }
500
501        $result = $ilDB->query($sql);
502        $participants = array();
503        if ($result->numRows() > 0) {
504            while ($row = $ilDB->fetchAssoc($result)) {
505                $userdata = $this->getUserDataFromActiveId($row["finished_id"], $force_non_anonymous);
506                $userdata["finished"] = (bool) $row["state"];
507                $userdata["finished_tstamp"] = $row["tstamp"];
508                $participants[$userdata["sortname"] . $userdata["active_id"]] = $userdata;
509            }
510        }
511        $participant_ids = array_column($participants, "usr_id");
512        if ($include_invites) {
513            foreach ($this->invitation_manager->getAllForSurvey($this->getSurveyId()) as $usr_id) {
514                if (!in_array($usr_id, $participant_ids)) {
515                    $name = ilObjUser::_lookupName($usr_id);
516                    $participants[$name["lastname"] . "," . $name["firstname"] . $usr_id] = [
517                        "fullname" => ilObjUser::_lookupFullname($usr_id),
518                        "sortname" => $name["lastname"] . "," . $name["firstname"],
519                        "fistname" => $name["firstname"],
520                        "lastname" => $name["lastname"],
521                        "login" => $name["login"],
522                        "gender" => "",
523                        "usr_id" => $usr_id,
524                        "finished" => false,
525                        "finished_tstamp" => 0,
526                        "invited" => true
527                    ];
528                }
529            }
530        }
531
532        return $participants;
533    }
534
535    /**
536    * Returns 1, if a survey is complete for use
537    *
538    * @return boolean 1, if the survey is complete for use, otherwise 0
539    * @access public
540    */
541    public function isComplete()
542    {
543        if (($this->getTitle()) and (count($this->questions))) {
544            return 1;
545        } else {
546            return 0;
547        }
548    }
549
550    /**
551    * Saves the completion status of the survey
552    *
553    * @access public
554    */
555    public function saveCompletionStatus()
556    {
557        $ilDB = $this->db;
558
559        $complete = 0;
560        if ($this->isComplete()) {
561            $complete = 1;
562        }
563        if ($this->getSurveyId() > 0) {
564            $affectedRows = $ilDB->manipulateF(
565                "UPDATE svy_svy SET complete = %s, tstamp = %s WHERE survey_id = %s",
566                array('text','integer','integer'),
567                array($this->isComplete(), time(), $this->getSurveyId())
568            );
569        }
570    }
571
572    /**
573    * Takes a question and creates a copy of the question for use in the survey
574    *
575    * @param integer $question_id The database id of the question
576    * @result integer The database id of the copied question
577    * @access public
578    */
579    public function duplicateQuestionForSurvey($question_id, $a_force = false)
580    {
581        $ilUser = $this->user;
582
583        $questiontype = $this->getQuestionType($question_id);
584        $question_gui = $this->getQuestionGUI($questiontype, $question_id);
585
586        // check if question is a pool question at all, if not do nothing
587        if ($this->getId() == $question_gui->object->getObjId() && !$a_force) {
588            return $question_id;
589        }
590
591        $duplicate_id = $question_gui->object->duplicate(true, "", "", "", $this->getId());
592        return $duplicate_id;
593    }
594
595    /**
596    * Inserts a question in the survey and saves the relation to the database
597    *
598    * @access public
599    */
600    public function insertQuestion($question_id)
601    {
602        $ilDB = $this->db;
603
604        $this->log->debug("insert question, id:" . $question_id);
605
606        if (!SurveyQuestion::_isComplete($question_id)) {
607            $this->log->debug("question is not complete");
608            return false;
609        } else {
610            // get maximum sequence index in test
611            // @todo: refactor this
612            $result = $ilDB->queryF(
613                "SELECT survey_question_id FROM svy_svy_qst WHERE survey_fi = %s",
614                array('integer'),
615                array($this->getSurveyId())
616            );
617            $sequence = $result->numRows();
618            $duplicate_id = $this->duplicateQuestionForSurvey($question_id);
619            $this->log->debug("duplicate, id: " . $question_id . ", duplicate id: " . $duplicate_id);
620
621            // check if question is not already in the survey, see #22018
622            if ($this->isQuestionInSurvey($duplicate_id)) {
623                return false;
624            }
625
626            $next_id = $ilDB->nextId('svy_svy_qst');
627            $affectedRows = $ilDB->manipulateF(
628                "INSERT INTO svy_svy_qst (survey_question_id, survey_fi, question_fi, sequence, tstamp) VALUES (%s, %s, %s, %s, %s)",
629                array('integer', 'integer', 'integer', 'integer', 'integer'),
630                array($next_id, $this->getSurveyId(), $duplicate_id, $sequence, time())
631            );
632
633            $this->log->debug("added entry to svy_svy_qst, id: " . $next_id . ", question id: " . $duplicate_id . ", sequence: " . $sequence);
634
635            $this->loadQuestionsFromDb();
636            return true;
637        }
638    }
639
640    /**
641     * Check if a question is already in the survey
642     *
643     * @param question id (as primary key from svy_question table)
644     * @return bool
645     */
646    public function isQuestionInSurvey($a_question_fi)
647    {
648        global $DIC;
649        //return false;
650        $ilDB = $DIC->database();
651
652        $set = $ilDB->query("SELECT * FROM svy_svy_qst " .
653            " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
654            " AND question_fi = " . $ilDB->quote($a_question_fi, "integer"));
655        if ($rec = $ilDB->fetchAssoc($set)) {
656            return true;
657        }
658        return false;
659    }
660
661
662
663    /**
664    * Inserts a questionblock in the survey and saves the relation to the database
665    *
666    * @access public
667    */
668    public function insertQuestionblock($questionblock_id)
669    {
670        $ilDB = $this->db;
671        $result = $ilDB->queryF(
672            "SELECT svy_qblk.title, svy_qblk.show_questiontext, svy_qblk.show_blocktitle," .
673            " svy_qblk_qst.question_fi FROM svy_qblk, svy_qblk_qst, svy_svy_qst" .
674            " WHERE svy_qblk.questionblock_id = svy_qblk_qst.questionblock_fi" .
675            " AND svy_svy_qst.question_fi = svy_qblk_qst.question_fi" .
676            " AND svy_qblk.questionblock_id = %s" .
677            " ORDER BY svy_svy_qst.sequence",
678            array('integer'),
679            array($questionblock_id)
680        );
681        $questions = array();
682        $show_questiontext = 0;
683        $show_blocktitle = 0;
684        while ($row = $ilDB->fetchAssoc($result)) {
685            $duplicate_id = $this->duplicateQuestionForSurvey($row["question_fi"]);
686            array_push($questions, $duplicate_id);
687            $title = $row["title"];
688            $show_questiontext = $row["show_questiontext"];
689            $show_blocktitle = $row["show_blocktitle"];
690        }
691        $this->createQuestionblock($title, $show_questiontext, $show_blocktitle, $questions);
692    }
693
694    public function saveUserSettings($usr_id, $key, $title, $value)
695    {
696        $ilDB = $this->db;
697
698        $next_id = $ilDB->nextId('svy_settings');
699        $affectedRows = $ilDB->insert("svy_settings", array(
700            "settings_id" => array("integer", $next_id),
701            "usr_id" => array("integer", $usr_id),
702            "keyword" => array("text", $key),
703            "title" => array("text", $title),
704            "value" => array("clob", $value)
705        ));
706    }
707
708    public function deleteUserSettings($id)
709    {
710        $ilDB = $this->db;
711
712        $affectedRows = $ilDB->manipulateF(
713            "DELETE FROM svy_settings WHERE settings_id = %s",
714            array('integer'),
715            array($id)
716        );
717        return $affectedRows;
718    }
719
720    public function getUserSettings($usr_id, $key)
721    {
722        $ilDB = $this->db;
723
724        $result = $ilDB->queryF(
725            "SELECT * FROM svy_settings WHERE usr_id = %s AND keyword = %s",
726            array('integer', 'text'),
727            array($usr_id, $key)
728        );
729        $found = array();
730        if ($result->numRows()) {
731            while ($row = $ilDB->fetchAssoc($result)) {
732                $found[$row['settings_id']] = $row;
733            }
734        }
735        return $found;
736    }
737
738    /**
739    * Saves a survey object to a database
740    *
741    * @access public
742    */
743    public function saveToDb()
744    {
745        $ilDB = $this->db;
746
747        // date handling
748        $rmd_start = $this->getReminderStart();
749        if (is_object($rmd_start)) {
750            $rmd_start = $rmd_start->get(IL_CAL_DATE);
751        }
752        $rmd_end = $this->getReminderEnd();
753        if (is_object($rmd_end)) {
754            $rmd_end = $rmd_end->get(IL_CAL_DATE);
755        }
756
757        if ($this->getSurveyId() < 1) {
758            $next_id = $ilDB->nextId('svy_svy');
759            $affectedRows = $ilDB->insert("svy_svy", array(
760                "survey_id" => array("integer", $next_id),
761                "obj_fi" => array("integer", $this->getId()),
762                "author" => array("text", $this->getAuthor()),
763                "introduction" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getIntroduction(), 0)),
764                "outro" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getOutro(), 0)),
765                "startdate" => array("text", $this->getStartDate()),
766                "enddate" => array("text", $this->getEndDate()),
767                "evaluation_access" => array("text", $this->getEvaluationAccess()),
768                "complete" => array("text", $this->isComplete()),
769                "created" => array("integer", time()),
770                "anonymize" => array("text", $this->getAnonymize()),
771                "show_question_titles" => array("text", $this->getShowQuestionTitles()),
772                "mailnotification" => array('integer', ($this->getMailNotification()) ? 1 : 0),
773                "mailaddresses" => array('text', strlen($this->getMailAddresses()) ? $this->getMailAddresses() : null),
774                "mailparticipantdata" => array('text', strlen($this->getMailParticipantData()) ? $this->getMailParticipantData() : null),
775                "tstamp" => array("integer", time()),
776                "template_id" => array("integer", $this->getTemplate()),
777                "pool_usage" => array("integer", $this->getPoolUsage()),
778                // Mode type
779                "mode" => array("integer", $this->getMode()),
780                // 360°
781                "mode_360_self_eval" => array("integer", $this->get360SelfEvaluation()),
782                "mode_360_self_rate" => array("integer", $this->get360SelfRaters()),
783                "mode_360_self_appr" => array("integer", $this->get360SelfAppraisee()),
784                "mode_360_results" => array("integer", $this->get360Results()),
785                // competences
786                "mode_skill_service" => array("integer", (int) $this->getSkillService()),
787                // Self Evaluation Only
788                "mode_self_eval_results" => array("integer", ilObjSurvey::RESULTS_SELF_EVAL_OWN),
789                // reminder/notification
790                "reminder_status" => array("integer", (int) $this->getReminderStatus()),
791                "reminder_start" => array("datetime", $rmd_start),
792                "reminder_end" => array("datetime", $rmd_end),
793                "reminder_frequency" => array("integer", (int) $this->getReminderFrequency()),
794                "reminder_target" => array("integer", (int) $this->getReminderTarget()),
795                "reminder_last_sent" => array("datetime", $this->getReminderLastSent()),
796                "reminder_tmpl" => array("text", $this->getReminderTemplate(true)),
797                "tutor_ntf_status" => array("integer", (int) $this->getTutorNotificationStatus()),
798                "tutor_ntf_reci" => array("text", implode(";", (array) $this->getTutorNotificationRecipients())),
799                "tutor_ntf_target" => array("integer", (int) $this->getTutorNotificationTarget()),
800                "own_results_view" => array("integer", $this->hasViewOwnResults()),
801                "own_results_mail" => array("integer", $this->hasMailOwnResults()),
802                "tutor_res_status" => array("integer", (int) $this->getTutorResultsStatus()),
803                "tutor_res_reci" => array("text", implode(";", (array) $this->getTutorResultsRecipients())),
804                "confirmation_mail" => array("integer", $this->hasMailConfirmation()),
805                "anon_user_list" => array("integer", $this->hasAnonymousUserList())
806            ));
807            $this->setSurveyId($next_id);
808        } else {
809            $affectedRows = $ilDB->update("svy_svy", array(
810                "author" => array("text", $this->getAuthor()),
811                "introduction" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getIntroduction(), 0)),
812                "outro" => array("clob", ilRTE::_replaceMediaObjectImageSrc($this->getOutro(), 0)),
813                "startdate" => array("text", $this->getStartDate()),
814                "enddate" => array("text", $this->getEndDate()),
815                "evaluation_access" => array("text", $this->getEvaluationAccess()),
816                "complete" => array("text", $this->isComplete()),
817                "anonymize" => array("text", $this->getAnonymize()),
818                "show_question_titles" => array("text", $this->getShowQuestionTitles()),
819                "mailnotification" => array('integer', ($this->getMailNotification()) ? 1 : 0),
820                "mailaddresses" => array('text', strlen($this->getMailAddresses()) ? $this->getMailAddresses() : null),
821                "mailparticipantdata" => array('text', strlen($this->getMailParticipantData()) ? $this->getMailParticipantData() : null),
822                "tstamp" => array("integer", time()),
823                "template_id" => array("integer", $this->getTemplate()),
824                "pool_usage" => array("integer", $this->getPoolUsage()),
825                //MODE TYPE
826                "mode" => array("integer", $this->getMode()),
827                // 360°
828                "mode_360_self_eval" => array("integer", $this->get360SelfEvaluation()),
829                "mode_360_self_rate" => array("integer", $this->get360SelfRaters()),
830                "mode_360_self_appr" => array("integer", $this->get360SelfAppraisee()),
831                "mode_360_results" => array("integer", $this->get360Results()),
832                // Competences
833                "mode_skill_service" => array("integer", (int) $this->getSkillService()),
834                // Self Evaluation Only
835                "mode_self_eval_results" => array("integer", $this->getSelfEvaluationResults()),
836                // reminder/notification
837                "reminder_status" => array("integer", $this->getReminderStatus()),
838                "reminder_start" => array("datetime", $rmd_start),
839                "reminder_end" => array("datetime", $rmd_end),
840                "reminder_frequency" => array("integer", $this->getReminderFrequency()),
841                "reminder_target" => array("integer", $this->getReminderTarget()),
842                "reminder_last_sent" => array("datetime", $this->getReminderLastSent()),
843                "reminder_tmpl" => array("text", $this->getReminderTemplate()),
844                "tutor_ntf_status" => array("integer", $this->getTutorNotificationStatus()),
845                "tutor_ntf_reci" => array("text", implode(";", (array) $this->getTutorNotificationRecipients())),
846                "tutor_ntf_target" => array("integer", $this->getTutorNotificationTarget()),
847                "own_results_view" => array("integer", $this->hasViewOwnResults()),
848                "own_results_mail" => array("integer", $this->hasMailOwnResults()),
849                "tutor_res_status" => array("integer", (int) $this->getTutorResultsStatus()),
850                "tutor_res_reci" => array("text", implode(";", (array) $this->getTutorResultsRecipients())),
851                "confirmation_mail" => array("integer", $this->hasMailConfirmation()),
852                "anon_user_list" => array("integer", $this->hasAnonymousUserList())
853            ), array(
854            "survey_id" => array("integer", $this->getSurveyId())
855            ));
856        }
857        if ($affectedRows) {
858            // save questions to db
859            $this->saveQuestionsToDb();
860        }
861
862        // moved activation to ilObjectActivation
863        if ($this->ref_id) {
864            ilObjectActivation::getItem($this->ref_id);
865
866            $item = new ilObjectActivation;
867            if (!$this->isActivationLimited()) {
868                $item->setTimingType(ilObjectActivation::TIMINGS_DEACTIVATED);
869            } else {
870                $item->setTimingType(ilObjectActivation::TIMINGS_ACTIVATION);
871                $item->setTimingStart($this->getActivationStartDate());
872                $item->setTimingEnd($this->getActivationEndDate());
873                $item->toggleVisible($this->getActivationVisibility());
874            }
875
876            $item->update($this->ref_id);
877        }
878    }
879
880    /**
881    * Saves the survey questions to the database
882    *
883    * @access public
884    * @see $questions
885    */
886    public function saveQuestionsToDb()
887    {
888        $ilDB = $this->db;
889
890        $this->log->debug("save questions");
891
892        // gather old questions state
893        $old_questions = array();
894        $result = $ilDB->queryF(
895            "SELECT survey_question_id,question_fi,sequence" .
896            " FROM svy_svy_qst WHERE survey_fi = %s",
897            array('integer'),
898            array($this->getSurveyId())
899        );
900        while ($row = $ilDB->fetchAssoc($result)) {
901            $old_questions[$row["question_fi"]] = $row;		// problem, as soon as duplicates exist, they will be hidden here
902        }
903
904        // #15231 - diff with current questions state
905        $insert = $update = $delete = array();
906        foreach ($this->questions as $seq => $fi) {
907            if (!array_key_exists($fi, $old_questions)) {		// really new fi IDs
908                $insert[] = $fi;							// this should be ok, should not create duplicates here
909            } elseif ($old_questions[$fi]["sequence"] != $seq) {				// we are updating one of the duplicates (if any)
910                $update[$fi] = $old_questions[$fi]["survey_question_id"];
911            }
912            // keep track of still relevant questions
913            unset($old_questions[$fi]);						// deleting old question, if they are not in current array
914        }
915
916        // delete obsolete question relations
917        if (sizeof($old_questions)) {
918            $del_ids = array();
919            foreach ($old_questions as $fi => $old) {
920                $del_ids[] = $old["survey_question_id"];
921            }
922            $ilDB->manipulate($q = "DELETE FROM svy_svy_qst" .
923                " WHERE " . $ilDB->in("survey_question_id", $del_ids, "", "integer"));
924            $this->log->debug("delete: " . $q);
925        }
926        unset($old_questions);
927
928        // create/update question relations
929        foreach ($this->questions as $seq => $fi) {
930            if (in_array($fi, $insert)) {
931                // check if question is not already in the survey, see #22018
932                if (!$this->isQuestionInSurvey($fi)) {
933                    $next_id = $ilDB->nextId('svy_svy_qst');
934                    $ilDB->manipulateF(
935                        "INSERT INTO svy_svy_qst" .
936                        " (survey_question_id, survey_fi, question_fi, heading, sequence, tstamp)" .
937                        " VALUES (%s, %s, %s, %s, %s, %s)",
938                        array('integer', 'integer', 'integer', 'text', 'integer', 'integer'),
939                        array($next_id, $this->getSurveyId(), $fi, null, $seq, time())
940                    );
941                    $this->log->debug("insert svy_svy_qst, id:" . $next_id . ", fi: " . $fi . ", seq:" . $seq);
942                }
943            } elseif (array_key_exists($fi, $update)) {
944                $ilDB->manipulate("UPDATE svy_svy_qst" .
945                    " SET sequence = " . $ilDB->quote($seq, "integer") .
946                    ", tstamp = " . $ilDB->quote(time(), "integer") .
947                    " WHERE survey_question_id = " . $ilDB->quote($update[$fi], "integer"));
948                $this->log->debug("update svy_svy_qst, id:" . $update[$fi] . ", fi: " . $fi . ", seq:" . $seq);
949            }
950        }
951    }
952
953    /**
954    * Checks for an anomyous survey id in the database an returns the id
955    *
956    * @param string $id A survey access code
957    * @result object Anonymous survey id if found, empty string otherwise
958    * @access public
959    */
960    public function getAnonymousId($id)
961    {
962        $ilDB = $this->db;
963        $result = $ilDB->queryF(
964            "SELECT anonymous_id FROM svy_finished WHERE anonymous_id = %s",
965            array('text'),
966            array($id)
967        );
968        if ($result->numRows()) {
969            $row = $ilDB->fetchAssoc($result);
970            return $row["anonymous_id"];
971        } else {
972            return "";
973        }
974    }
975
976    /**
977    * Returns a question gui object to a given questiontype and question id
978    *
979    * @result object Resulting question gui object
980    * @access public
981    */
982    public function getQuestionGUI($questiontype, $question_id)
983    {
984        return SurveyQuestionGUI::_getQuestionGUI($questiontype, $question_id);
985    }
986
987    /**
988    * Returns the question type of a question with a given id
989    *
990    * @param integer $question_id The database id of the question
991    * @result string The question type string
992    * @access private
993    */
994    public function getQuestionType($question_id)
995    {
996        $ilDB = $this->db;
997        if ($question_id < 1) {
998            return -1;
999        }
1000        $result = $ilDB->queryF(
1001            "SELECT type_tag FROM svy_question, svy_qtype WHERE svy_question.question_id = %s AND " .
1002            "svy_question.questiontype_fi = svy_qtype.questiontype_id",
1003            array('integer'),
1004            array($question_id)
1005        );
1006        if ($result->numRows() == 1) {
1007            $data = $ilDB->fetchAssoc($result);
1008            return $data["type_tag"];
1009        } else {
1010            return "";
1011        }
1012    }
1013
1014    /**
1015    * Returns the survey database id
1016    *
1017    * @result integer survey database id
1018    * @access public
1019    */
1020    public function getSurveyId()
1021    {
1022        return $this->survey_id;
1023    }
1024
1025    /**
1026    * set anonymize status
1027    */
1028    public function setAnonymize($a_anonymize)
1029    {
1030        switch ($a_anonymize) {
1031            case self::ANONYMIZE_OFF:
1032            case self::ANONYMIZE_ON:
1033            case self::ANONYMIZE_FREEACCESS:
1034            case self::ANONYMIZE_CODE_ALL:
1035                $this->anonymize = $a_anonymize;
1036                break;
1037            default:
1038                $this->anonymize = self::ANONYMIZE_OFF;
1039                break;
1040        }
1041    }
1042
1043    /**
1044    * get anonymize status
1045    *
1046    * @return	integer status
1047    */
1048    public function getAnonymize()
1049    {
1050        return ($this->anonymize) ? $this->anonymize : 0;
1051    }
1052
1053    /**
1054    * Checks if the survey is accessable without a survey code
1055    *
1056    * @return	boolean status
1057    */
1058    public function isAccessibleWithoutCode()
1059    {
1060        return ($this->getAnonymize() == self::ANONYMIZE_OFF ||
1061            $this->getAnonymize() == self::ANONYMIZE_FREEACCESS);
1062    }
1063
1064    /**
1065    * Checks if the survey results are to be anonymized
1066    *
1067    * @return	boolean status
1068    */
1069    public function hasAnonymizedResults()
1070    {
1071        return ($this->getAnonymize() == self::ANONYMIZE_ON ||
1072            $this->getAnonymize() == self::ANONYMIZE_FREEACCESS);
1073    }
1074
1075    /**
1076    * Loads a survey object from a database
1077    *
1078    * @access public
1079    */
1080    public function loadFromDb()
1081    {
1082        $ilDB = $this->db;
1083        $result = $ilDB->queryF(
1084            "SELECT * FROM svy_svy WHERE obj_fi = %s",
1085            array('integer'),
1086            array($this->getId())
1087        );
1088        if ($result->numRows() == 1) {
1089            $data = $ilDB->fetchAssoc($result);
1090            $this->setSurveyId($data["survey_id"]);
1091            $this->setAuthor($data["author"]);
1092            $this->setIntroduction(ilRTE::_replaceMediaObjectImageSrc($data["introduction"], 1));
1093            if (strcmp($data["outro"], "survey_finished") == 0) {
1094                $this->setOutro($this->lng->txt("survey_finished"));
1095            } else {
1096                $this->setOutro(ilRTE::_replaceMediaObjectImageSrc($data["outro"], 1));
1097            }
1098            $this->setShowQuestionTitles($data["show_question_titles"]);
1099            $this->setStartDate($data["startdate"]);
1100            $this->setEndDate($data["enddate"]);
1101            $this->setAnonymize($data["anonymize"]);
1102            $this->setEvaluationAccess($data["evaluation_access"]);
1103            $this->loadQuestionsFromDb();
1104            $this->setMailNotification($data['mailnotification']);
1105            $this->setMailAddresses($data['mailaddresses']);
1106            $this->setMailParticipantData($data['mailparticipantdata']);
1107            $this->setTemplate($data['template_id']);
1108            $this->setPoolUsage($data['pool_usage']);
1109            // Mode
1110            $this->setMode($data['mode']);
1111            // 360°
1112            $this->set360SelfEvaluation($data['mode_360_self_eval']);
1113            $this->set360SelfRaters($data['mode_360_self_rate']);
1114            $this->set360SelfAppraisee($data['mode_360_self_appr']);
1115            $this->set360Results($data['mode_360_results']);
1116            // Mode self evaluated
1117            $this->setSelfEvaluationResults($data['mode_self_eval_results']);
1118            // Competences
1119            $this->setSkillService($data['mode_skill_service']);
1120            // reminder/notification
1121            $this->setReminderStatus($data["reminder_status"]);
1122            $this->setReminderStart($data["reminder_start"] ? new ilDate($data["reminder_start"], IL_CAL_DATE) : null);
1123            $this->setReminderEnd($data["reminder_end"] ? new ilDate($data["reminder_end"], IL_CAL_DATE) : null);
1124            $this->setReminderFrequency($data["reminder_frequency"]);
1125            $this->setReminderTarget($data["reminder_target"]);
1126            $this->setReminderLastSent($data["reminder_last_sent"]);
1127            $this->setReminderTemplate($data["reminder_tmpl"]);
1128            $this->setTutorNotificationStatus($data["tutor_ntf_status"]);
1129            $this->setTutorNotificationRecipients(explode(";", $data["tutor_ntf_reci"]));
1130            $this->setTutorNotificationTarget($data["tutor_ntf_target"]);
1131            $this->setTutorResultsStatus($data["tutor_res_status"]);
1132            $this->setTutorResultsRecipients(explode(";", $data["tutor_res_reci"]));
1133
1134            $this->setViewOwnResults($data["own_results_view"]);
1135            $this->setMailOwnResults($data["own_results_mail"]);
1136            $this->setMailConfirmation($data["confirmation_mail"]);
1137
1138            $this->setAnonymousUserList($data["anon_user_list"]);
1139        }
1140
1141        // moved activation to ilObjectActivation
1142        if ($this->ref_id) {
1143            $activation = ilObjectActivation::getItem($this->ref_id);
1144            switch ($activation["timing_type"]) {
1145                case ilObjectActivation::TIMINGS_ACTIVATION:
1146                    $this->setActivationLimited(true);
1147                    $this->setActivationStartDate($activation["timing_start"]);
1148                    $this->setActivationEndDate($activation["timing_end"]);
1149                    $this->setActivationVisibility($activation["visible"]);
1150                    break;
1151
1152                default:
1153                    $this->setActivationLimited(false);
1154                    break;
1155            }
1156        }
1157    }
1158
1159    /**
1160    * Loads the survey questions from the database
1161    *
1162    * @access public
1163    * @see $questions
1164    */
1165    public function loadQuestionsFromDb()
1166    {
1167        $ilDB = $this->db;
1168        $this->questions = array();
1169        $result = $ilDB->queryF(
1170            "SELECT * FROM svy_svy_qst WHERE survey_fi = %s ORDER BY sequence",
1171            array('integer'),
1172            array($this->getSurveyId())
1173        );
1174        while ($data = $ilDB->fetchAssoc($result)) {
1175            $this->questions[$data["sequence"]] = $data["question_fi"];
1176        }
1177    }
1178
1179    /**
1180     * Remove duplicate sequence entries, see #22018
1181     */
1182    public function fixSequenceStructure()
1183    {
1184        global $DIC;
1185
1186        $ilDB = $DIC->database();
1187        //return;
1188        // we keep all survey question ids with their lowest sequence
1189        $result = $ilDB->queryF(
1190            "SELECT * FROM svy_svy_qst WHERE survey_fi = %s ORDER BY sequence",
1191            array('integer'),
1192            array($this->getSurveyId())
1193        );
1194
1195        // step 1: find duplicates -> $to_delete_ids
1196        $fis = array();
1197        $to_delete_ids = array();
1198        while ($data = $ilDB->fetchAssoc($result)) {
1199            if (in_array($data["question_fi"], $fis)) {		// found a duplicate
1200                $to_delete_ids[] = $data["survey_question_id"];
1201            } else {
1202                $fis[] = $data["question_fi"];
1203            }
1204        }
1205
1206        // step 2: we delete the duplicates
1207        if (count($to_delete_ids) > 0) {
1208            $ilDB->manipulate($q = "DELETE FROM svy_svy_qst" .
1209                " WHERE " . $ilDB->in("survey_question_id", $to_delete_ids, false, "integer") .
1210                " AND survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer"));
1211            $this->log->debug("delete: " . $q);
1212
1213            $ilDB->manipulate($q = "DELETE FROM svy_qblk_qst " .
1214                " WHERE " . $ilDB->in("question_fi", $fis, true, "integer") .
1215                " AND survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer"));
1216            $this->log->debug("delete: " . $q);
1217        }
1218
1219        // step 3: we fix the sequence
1220        $set = $ilDB->query("SELECT * FROM svy_svy_qst " .
1221            " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") . " ORDER BY sequence");
1222        $seq = 0;
1223        while ($rec = $ilDB->fetchAssoc($set)) {
1224            $ilDB->manipulate(
1225                $q = "UPDATE svy_svy_qst SET " .
1226                " sequence = " . $ilDB->quote($seq++, "integer") .
1227                " WHERE survey_question_id = " . $ilDB->quote($rec["survey_question_id"], "integer")
1228            );
1229            $this->log->debug("update: " . $q);
1230        }
1231    }
1232
1233
1234    /**
1235    * Sets the authors name of the ilObjSurvey object
1236    *
1237    * @param string $author A string containing the name of the test author
1238    * @access public
1239    * @see $author
1240    */
1241    public function setAuthor($author = "")
1242    {
1243        $this->author = $author;
1244    }
1245
1246    /**
1247    * Saves an authors name into the lifecycle metadata if no lifecycle metadata exists
1248    * This will only be called for conversion of "old" tests where the author hasn't been
1249    * stored in the lifecycle metadata
1250    *
1251    * @param string $a_author A string containing the name of the test author
1252    * @access private
1253    * @see $author
1254    */
1255    public function saveAuthorToMetadata($a_author = "")
1256    {
1257        $md = new ilMD($this->getId(), 0, $this->getType());
1258        $md_life = &$md->getLifecycle();
1259        if (!$md_life) {
1260            if (strlen($a_author) == 0) {
1261                $ilUser = $this->user;
1262                $a_author = $ilUser->getFullname();
1263            }
1264
1265            $md_life = &$md->addLifecycle();
1266            $md_life->save();
1267            $con = &$md_life->addContribute();
1268            $con->setRole("Author");
1269            $con->save();
1270            $ent = &$con->addEntity();
1271            $ent->setEntity($a_author);
1272            $ent->save();
1273        }
1274    }
1275
1276    /**
1277    * Gets the authors name of the ilObjSurvey object
1278    *
1279    * @return string The string containing the name of the test author
1280    * @access public
1281    * @see $author
1282    */
1283    public function getAuthor()
1284    {
1285        $author = array();
1286        $md = new ilMD($this->getId(), 0, $this->getType());
1287        $md_life = &$md->getLifecycle();
1288        if ($md_life) {
1289            $ids = &$md_life->getContributeIds();
1290            foreach ($ids as $id) {
1291                $md_cont = &$md_life->getContribute($id);
1292                if (strcmp($md_cont->getRole(), "Author") == 0) {
1293                    $entids = &$md_cont->getEntityIds();
1294                    foreach ($entids as $entid) {
1295                        $md_ent = &$md_cont->getEntity($entid);
1296                        array_push($author, $md_ent->getEntity());
1297                    }
1298                }
1299            }
1300        }
1301        return join(",", $author);
1302    }
1303
1304    /**
1305    * Gets the status of the display_question_titles attribute
1306    *
1307    * @return integer The status of the display_question_titles attribute
1308    * @see $display_question_titles
1309    */
1310    public function getShowQuestionTitles()
1311    {
1312        return ($this->display_question_titles) ? 1 : 0;
1313    }
1314
1315    /**
1316    * Sets the status of the display_question_titles attribute
1317    *
1318    * @param integer $a_show The status of the display_question_titles attribute
1319    * @see $display_question_titles
1320    */
1321    public function setShowQuestionTitles($a_show)
1322    {
1323        $this->display_question_titles = ($a_show) ? 1 : 0;
1324    }
1325
1326    /**
1327    * Sets the question titles visible during the query
1328    *
1329    * @access public
1330    * @see $display_question_titles
1331    */
1332    public function showQuestionTitles()
1333    {
1334        $this->display_question_titles = 1;
1335    }
1336
1337    /**
1338    * Sets the question titles hidden during the query
1339    *
1340    * @access public
1341    * @see $display_question_titles
1342    */
1343    public function hideQuestionTitles()
1344    {
1345        $this->display_question_titles = 0;
1346    }
1347
1348    /**
1349    * Sets the introduction text
1350    *
1351    * @param string $introduction A string containing the introduction
1352    * @see $introduction
1353    */
1354    public function setIntroduction($introduction = "")
1355    {
1356        $this->introduction = $introduction;
1357    }
1358
1359    /**
1360    * Sets the outro text
1361    *
1362    * @param string $outro A string containing the outro
1363    * @see $outro
1364    */
1365    public function setOutro($outro = "")
1366    {
1367        $this->outro = $outro;
1368    }
1369
1370
1371    /**
1372    * Gets the start date of the survey
1373    *
1374    * @return string Survey start date (YYYY-MM-DD)
1375    * @access public
1376    * @see $start_date
1377    */
1378    public function getStartDate()
1379    {
1380        return (strlen($this->start_date)) ? $this->start_date : null;
1381    }
1382
1383    /**
1384    * Checks if the survey can be started
1385    *
1386    * @return array An array containing the following keys: result (boolean) and messages (array)
1387    * @access public
1388    */
1389    public function canStartSurvey($anonymous_id = null, $a_no_rbac = false)
1390    {
1391        $ilAccess = $this->access;
1392
1393        $result = true;
1394        $messages = array();
1395        $edit_settings = false;
1396        // check start date
1397        if (preg_match("/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/", $this->getStartDate(), $matches)) {
1398            $epoch_time = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
1399            $now = time();
1400            if ($now < $epoch_time) {
1401                array_push($messages, $this->lng->txt('start_date_not_reached') . ' (' .
1402                    ilDatePresentation::formatDate(new ilDateTime($this->getStartDate(), IL_CAL_TIMESTAMP)) . ")");
1403                $result = false;
1404                $edit_settings = true;
1405            }
1406        }
1407        // check end date
1408        if (preg_match("/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/", $this->getEndDate(), $matches)) {
1409            $epoch_time = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
1410            $now = time();
1411            if ($now > $epoch_time) {
1412                array_push($messages, $this->lng->txt('end_date_reached') . ' (' .
1413                    ilDatePresentation::formatDate(new ilDateTime($this->getEndDate(), IL_CAL_TIMESTAMP)) . ")");
1414                $result = false;
1415                $edit_settings = true;
1416            }
1417        }
1418
1419        // check online status
1420        if ($this->getOfflineStatus()) {
1421            array_push($messages, $this->lng->txt("survey_is_offline"));
1422            $result = false;
1423            $edit_settings = true;
1424        }
1425        // check rbac permissions
1426        if (!$a_no_rbac && !$ilAccess->checkAccess("read", "", $this->ref_id)) {
1427            array_push($messages, $this->lng->txt("cannot_participate_survey"));
1428            $result = false;
1429        }
1430        /*
1431        // 2. check previous access
1432        if (!$result["error"])
1433        {
1434        $ilUser = $this->user;
1435            $survey_started = $this->isSurveyStarted($ilUser->getId(), $anonymous_id);
1436            if ($survey_started === 1)
1437            {
1438                array_push($messages, $this->lng->txt("already_completed_survey"));
1439                $result = FALSE;
1440            }
1441        }
1442        */
1443        return array(
1444            "result" => $result,
1445            "messages" => $messages,
1446            "edit_settings" => $edit_settings
1447        );
1448    }
1449
1450    /**
1451    * Sets the start date of the survey
1452    *
1453    * @param string $start_data Survey start date (YYYYMMDDHHMMSS)
1454    * @access public
1455    * @see $start_date
1456    */
1457    public function setStartDate($start_date = "")
1458    {
1459        $this->start_date = $start_date;
1460    }
1461
1462    /**
1463    * Sets the start date of the survey
1464    *
1465    * @param string $start_date Survey start date (YYYY-MM-DD)
1466    * @param string $start_time Survey start time (HH:MM:SS)
1467    * @access public
1468    * @see $start_date
1469    */
1470    public function setStartDateAndTime($start_date = "", $start_time)
1471    {
1472        $y = '';
1473        $m = '';
1474        $d = '';
1475        $h = '';
1476        $i = '';
1477        $s = '';
1478        if (preg_match("/(\d{4})-(\d{2})-(\d{2})/", $start_date, $matches)) {
1479            $y = $matches[1];
1480            $m = $matches[2];
1481            $d = $matches[3];
1482        }
1483        if (preg_match("/(\d{2}):(\d{2}):(\d{2})/", $start_time, $matches)) {
1484            $h = $matches[1];
1485            $i = $matches[2];
1486            $s = $matches[3];
1487        }
1488        $this->start_date = sprintf('%04d%02d%02d%02d%02d%02d', $y, $m, $d, $h, $i, $s);
1489    }
1490
1491    /**
1492    * Gets the end date of the survey
1493    *
1494    * @return string Survey end date (YYYY-MM-DD)
1495    * @access public
1496    * @see $end_date
1497    */
1498    public function getEndDate()
1499    {
1500        return (strlen($this->end_date)) ? $this->end_date : null;
1501    }
1502
1503    /**
1504    * Sets the end date of the survey
1505    *
1506    * @param string $end_date Survey end date (YYYYMMDDHHMMSS)
1507    * @access public
1508    * @see $end_date
1509    */
1510    public function setEndDate($end_date = "")
1511    {
1512        $this->end_date = $end_date;
1513    }
1514
1515    /**
1516    * Sets the end date of the survey
1517    *
1518    * @param string $end_date Survey end date (YYYY-MM-DD)
1519    * @param string $end_time Survey end time (HH:MM:SS)
1520    * @access public
1521    * @see $start_date
1522    */
1523    public function setEndDateAndTime($end_date = "", $end_time)
1524    {
1525        $y = '';
1526        $m = '';
1527        $d = '';
1528        $h = '';
1529        $i = '';
1530        $s = '';
1531        if (preg_match("/(\d{4})-(\d{2})-(\d{2})/", $end_date, $matches)) {
1532            $y = $matches[1];
1533            $m = $matches[2];
1534            $d = $matches[3];
1535        }
1536        if (preg_match("/(\d{2}):(\d{2}):(\d{2})/", $end_time, $matches)) {
1537            $h = $matches[1];
1538            $i = $matches[2];
1539            $s = $matches[3];
1540        }
1541        $this->end_date = sprintf('%04d%02d%02d%02d%02d%02d', $y, $m, $d, $h, $i, $s);
1542    }
1543
1544    /**
1545    * Gets the learners evaluation access
1546    *
1547    * @return integer The evaluation access
1548    * @access public
1549    * @see $evaluation_access
1550    */
1551    public function getEvaluationAccess()
1552    {
1553        return ($this->evaluation_access) ? $this->evaluation_access : self::EVALUATION_ACCESS_OFF;
1554    }
1555
1556    /**
1557    * Sets the learners evaluation access
1558    *
1559    * @param integer $evaluation_access The evaluation access
1560    * @access public
1561    * @see $evaluation_access
1562    */
1563    public function setEvaluationAccess($evaluation_access = self::EVALUATION_ACCESS_OFF)
1564    {
1565        $this->evaluation_access = ($evaluation_access) ? $evaluation_access : self::EVALUATION_ACCESS_OFF;
1566    }
1567
1568    public function setActivationVisibility($a_value)
1569    {
1570        $this->activation_visibility = (bool) $a_value;
1571    }
1572
1573    public function getActivationVisibility()
1574    {
1575        return $this->activation_visibility;
1576    }
1577
1578    public function isActivationLimited()
1579    {
1580        return (bool) $this->activation_limited;
1581    }
1582
1583    public function setActivationLimited($a_value)
1584    {
1585        $this->activation_limited = (bool) $a_value;
1586    }
1587
1588    /**
1589    * Gets the introduction text
1590    *
1591    * @return string The introduction of the survey object
1592    * @access public
1593    * @see $introduction
1594    */
1595    public function getIntroduction()
1596    {
1597        return (strlen($this->introduction)) ? $this->introduction : null;
1598    }
1599
1600    /**
1601    * Gets the outro text
1602    *
1603    * @return string The outro of the survey object
1604    * @access public
1605    * @see $outro
1606    */
1607    public function getOutro()
1608    {
1609        return (strlen($this->outro)) ? $this->outro : null;
1610    }
1611
1612    /**
1613    * Gets the question id's of the questions which are already in the survey
1614    *
1615    * @return array The questions of the survey
1616    * @access public
1617    */
1618    public function &getExistingQuestions()
1619    {
1620        $ilDB = $this->db;
1621        $existing_questions = array();
1622        $result = $ilDB->queryF(
1623            "SELECT svy_question.original_id FROM svy_question, svy_svy_qst WHERE " .
1624            "svy_svy_qst.survey_fi = %s AND svy_svy_qst.question_fi = svy_question.question_id",
1625            array('integer'),
1626            array($this->getSurveyId())
1627        );
1628        while ($data = $ilDB->fetchAssoc($result)) {
1629            if ($data["original_id"]) {
1630                array_push($existing_questions, $data["original_id"]);
1631            }
1632        }
1633        return $existing_questions;
1634    }
1635
1636    /**
1637    * Get the titles of all available survey question pools
1638    *
1639    * @return array An array of survey question pool titles
1640    * @access public
1641    */
1642    public function &getQuestionpoolTitles($could_be_offline = false, $showPath = false)
1643    {
1644        return ilObjSurveyQuestionPool::_getAvailableQuestionpools($use_object_id = true, $could_be_offline, $showPath);
1645    }
1646
1647    /**
1648    * Move questions and/or questionblocks to another position
1649    *
1650    * @param array $move_questions An array with the question id's of the questions to move
1651    * @param integer $target_index The question id of the target position
1652    * @param integer $insert_mode 0, if insert before the target position, 1 if insert after the target position
1653    * @access public
1654    */
1655    public function moveQuestions($move_questions, $target_index, $insert_mode)
1656    {
1657        $array_pos = array_search($target_index, $this->questions);
1658        if ($insert_mode == 0) {
1659            $part1 = array_slice($this->questions, 0, $array_pos);
1660            $part2 = array_slice($this->questions, $array_pos);
1661        } elseif ($insert_mode == 1) {
1662            $part1 = array_slice($this->questions, 0, $array_pos + 1);
1663            $part2 = array_slice($this->questions, $array_pos + 1);
1664        }
1665        $found = 0;
1666        foreach ($move_questions as $question_id) {
1667            if (!(array_search($question_id, $part1) === false)) {
1668                unset($part1[array_search($question_id, $part1)]);
1669                $found++;
1670            }
1671            if (!(array_search($question_id, $part2) === false)) {
1672                unset($part2[array_search($question_id, $part2)]);
1673                $found++;
1674            }
1675        }
1676        // sanity check: do not move questions if they have not be found in the array
1677        if ($found != count($move_questions)) {
1678            return;
1679        }
1680        $part1 = array_values($part1);
1681        $part2 = array_values($part2);
1682        $this->questions = array_values(array_merge($part1, $move_questions, $part2));
1683        foreach ($move_questions as $question_id) {
1684            $constraints = $this->getConstraints($question_id);
1685            foreach ($constraints as $idx => $constraint) {
1686                foreach ($part2 as $next_question_id) {
1687                    if ($constraint["question"] == $next_question_id) {
1688                        // constraint concerning a question that follows -> delete constraint
1689                        $this->deleteConstraint($constraint["id"]);
1690                    }
1691                }
1692            }
1693        }
1694        $this->saveQuestionsToDb();
1695    }
1696
1697    /**
1698    * Remove a question from the survey
1699    *
1700    * @param integer $question_id The database id of the question
1701    * @access public
1702    */
1703    public function removeQuestion($question_id)
1704    {
1705        $question = self::_instanciateQuestion($question_id);
1706        #20610 if no question found, do nothing.
1707        if ($question) {
1708            $question->delete($question_id);
1709            $this->removeConstraintsConcerningQuestion($question_id);
1710        }
1711    }
1712
1713    /**
1714    * Remove constraints concerning a question with a given question_id
1715    *
1716    * @param integer $question_id The database id of the question
1717    * @access public
1718    */
1719    public function removeConstraintsConcerningQuestion($question_id)
1720    {
1721        $ilDB = $this->db;
1722        $result = $ilDB->queryF(
1723            "SELECT constraint_fi FROM svy_qst_constraint WHERE question_fi = %s AND survey_fi = %s",
1724            array('integer','integer'),
1725            array($question_id, $this->getSurveyId())
1726        );
1727        if ($result->numRows() > 0) {
1728            $remove_constraints = array();
1729            while ($row = $ilDB->fetchAssoc($result)) {
1730                array_push($remove_constraints, $row["constraint_fi"]);
1731            }
1732            $affectedRows = $ilDB->manipulateF(
1733                "DELETE FROM svy_qst_constraint WHERE question_fi = %s AND survey_fi = %s",
1734                array('integer','integer'),
1735                array($question_id, $this->getSurveyId())
1736            );
1737            foreach ($remove_constraints as $key => $constraint_id) {
1738                $affectedRows = $ilDB->manipulateF(
1739                    "DELETE FROM svy_constraint WHERE constraint_id = %s",
1740                    array('integer'),
1741                    array($constraint_id)
1742                );
1743            }
1744        }
1745    }
1746
1747    /**
1748    * Remove questions from the survey
1749    *
1750    * @param array $remove_questions An array with the question id's of the questions to remove
1751    * @param array $remove_questionblocks An array with the questionblock id's of the questions blocks to remove
1752    * @access public
1753    */
1754    public function removeQuestions($remove_questions, $remove_questionblocks)
1755    {
1756        $ilDB = $this->db;
1757
1758        $block_sizes = array();
1759        foreach ($this->getSurveyQuestions() as $question_id => $data) {
1760            if (in_array($question_id, $remove_questions) or in_array($data["questionblock_id"], $remove_questionblocks)) {
1761                unset($this->questions[array_search($question_id, $this->questions)]);
1762                $this->removeQuestion($question_id);
1763            } elseif ($data["questionblock_id"]) {
1764                $block_sizes[$data["questionblock_id"]]++;
1765            }
1766        }
1767
1768        // blocks with just 1 question need to be deleted
1769        foreach ($block_sizes as $block_id => $size) {
1770            if ($size < 2) {
1771                $remove_questionblocks[] = $block_id;
1772            }
1773        }
1774
1775        foreach (array_unique($remove_questionblocks) as $questionblock_id) {
1776            $affectedRows = $ilDB->manipulateF(
1777                "DELETE FROM svy_qblk WHERE questionblock_id = %s",
1778                array('integer'),
1779                array($questionblock_id)
1780            );
1781            $affectedRows = $ilDB->manipulateF(
1782                "DELETE FROM svy_qblk_qst WHERE questionblock_fi = %s AND survey_fi = %s",
1783                array('integer','integer'),
1784                array($questionblock_id, $this->getSurveyId())
1785            );
1786        }
1787
1788        $this->questions = array_values($this->questions);
1789        $this->saveQuestionsToDb();
1790    }
1791
1792    /**
1793    * Unfolds question blocks of a question pool
1794    *
1795    * @param array $questionblocks An array of question block id's
1796    * @access public
1797    */
1798    public function unfoldQuestionblocks($questionblocks)
1799    {
1800        $ilDB = $this->db;
1801        foreach ($questionblocks as $index) {
1802            $affectedRows = $ilDB->manipulateF(
1803                "DELETE FROM svy_qblk WHERE questionblock_id = %s",
1804                array('integer'),
1805                array($index)
1806            );
1807            $affectedRows = $ilDB->manipulateF(
1808                "DELETE FROM svy_qblk_qst WHERE questionblock_fi = %s AND survey_fi = %s",
1809                array('integer','integer'),
1810                array($index, $this->getSurveyId())
1811            );
1812        }
1813    }
1814
1815    public function removeQuestionFromBlock($question_id, $questionblock_id)
1816    {
1817        $ilDB = $this->db;
1818
1819        $affectedRows = $ilDB->manipulateF(
1820            "DELETE FROM svy_qblk_qst WHERE questionblock_fi = %s AND survey_fi = %s AND question_fi = %s",
1821            array('integer','integer','integer'),
1822            array($questionblock_id, $this->getSurveyId(), $question_id)
1823        );
1824    }
1825
1826    public function addQuestionToBlock($question_id, $questionblock_id)
1827    {
1828        $ilDB = $this->db;
1829
1830        // see #22018
1831        if (!$this->isQuestionInAnyBlock($question_id)) {
1832            $next_id = $ilDB->nextId('svy_qblk_qst');
1833            $affectedRows = $ilDB->manipulateF(
1834                "INSERT INTO svy_qblk_qst (qblk_qst_id, survey_fi, questionblock_fi, " .
1835                "question_fi) VALUES (%s, %s, %s, %s)",
1836                array('integer', 'integer', 'integer', 'integer'),
1837                array($next_id, $this->getSurveyId(), $questionblock_id, $question_id)
1838            );
1839
1840            $this->deleteConstraints($question_id); // #13713
1841        }
1842    }
1843
1844    /**
1845     * Is question already in a block?
1846     *
1847     * @param int $a_question_fi question id as in svy_question
1848     * @return bool
1849     */
1850    public function isQuestionInAnyBlock($a_question_fi)
1851    {
1852        global $DIC;
1853
1854        $ilDB = $DIC->database();
1855
1856        $set = $ilDB->query("SELECT * FROM svy_qblk_qst " .
1857            " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
1858            " AND question_fi = " . $ilDB->quote($a_question_fi, "integer"));
1859        if ($rec = $ilDB->fetchAssoc($set)) {
1860            return true;
1861        }
1862        return false;
1863    }
1864
1865
1866    /**
1867    * Returns the question titles of all questions of a question block
1868    *
1869    * @result array The titles of the the question block questions
1870    * @access public
1871    */
1872    public function &getQuestionblockQuestions($questionblock_id)
1873    {
1874        $ilDB = $this->db;
1875        $titles = array();
1876        $result = $ilDB->queryF(
1877            "SELECT svy_question.title, svy_qblk_qst.question_fi, svy_qblk_qst.survey_fi FROM " .
1878            "svy_qblk, svy_qblk_qst, svy_question WHERE svy_qblk.questionblock_id = svy_qblk_qst.questionblock_fi AND " .
1879            "svy_question.question_id = svy_qblk_qst.question_fi AND svy_qblk.questionblock_id = %s",
1880            array('integer'),
1881            array($questionblock_id)
1882        );
1883        $survey_id = "";
1884        while ($row = $ilDB->fetchAssoc($result)) {
1885            $titles[$row["question_fi"]] = $row["title"];
1886            $survey_id = $row["survey_fi"];
1887        }
1888        $result = $ilDB->queryF(
1889            "SELECT question_fi, sequence FROM svy_svy_qst WHERE survey_fi = %s ORDER BY sequence",
1890            array('integer'),
1891            array($survey_id)
1892        );
1893        $resultarray = array();
1894        $counter = 1;
1895        while ($row = $ilDB->fetchAssoc($result)) {
1896            if (array_key_exists($row["question_fi"], $titles)) {
1897                $resultarray[$counter++] = $titles[$row["question_fi"]];
1898            }
1899        }
1900        return $resultarray;
1901    }
1902
1903    /**
1904    * Returns the question id's of all questions of a question block
1905    *
1906    * @result array The id's of the the question block questions
1907    * @access public
1908    */
1909    public function &getQuestionblockQuestionIds($questionblock_id)
1910    {
1911        $ilDB = $this->db;
1912
1913        // we need a correct order here, see #22011
1914        $result = $ilDB->queryF(
1915            "SELECT a.question_fi FROM svy_qblk_qst a JOIN svy_svy_qst b ON (a.question_fi = b.question_fi) " .
1916            " WHERE a.questionblock_fi = %s ORDER BY b.sequence",
1917            array("integer"),
1918            array($questionblock_id)
1919        );
1920        $ids = array();
1921        if ($result->numRows()) {
1922            while ($data = $ilDB->fetchAssoc($result)) {
1923                if (!in_array($data['question_fi'], $ids)) {		// no duplicates, see #22018
1924                    array_push($ids, $data['question_fi']);
1925                }
1926            }
1927        }
1928
1929        return $ids;
1930    }
1931
1932    /**
1933    * Returns the database row for a given question block
1934    *
1935    * @param integer $questionblock_id The database id of the question block
1936    * @result array The database row of the question block
1937    * @access public
1938    */
1939    public static function _getQuestionblock($questionblock_id)
1940    {
1941        global $DIC;
1942
1943        $ilDB = $DIC->database();
1944        $result = $ilDB->queryF(
1945            "SELECT * FROM svy_qblk WHERE questionblock_id = %s",
1946            array('integer'),
1947            array($questionblock_id)
1948        );
1949        $row = $ilDB->fetchAssoc($result);
1950        return $row;
1951    }
1952
1953    /**
1954    * Adds a questionblock to the database
1955    *
1956    * @param string $title The questionblock title
1957    * @param integer $owner The database id of the owner
1958    * @return integer The database id of the newly created questionblock
1959    * @access public
1960    */
1961    public static function _addQuestionblock($title = "", $owner = 0, $show_questiontext = true, $show_blocktitle = false)
1962    {
1963        global $DIC;
1964
1965        $ilDB = $DIC->database();
1966        $next_id = $ilDB->nextId('svy_qblk');
1967        $ilDB->manipulateF(
1968            "INSERT INTO svy_qblk (questionblock_id, title, show_questiontext," .
1969            " show_blocktitle, owner_fi, tstamp) " .
1970            "VALUES (%s, %s, %s, %s, %s, %s)",
1971            array('integer','text','integer','integer','integer','integer'),
1972            array($next_id, $title, $show_questiontext, $show_blocktitle, $owner, time())
1973        );
1974        return $next_id;
1975    }
1976
1977    /**
1978    * Creates a question block for the survey
1979    *
1980    * @param string $title The title of the question block
1981    * @param array $questions An array with the database id's of the question block questions
1982    * @access public
1983    */
1984    public function createQuestionblock($title, $show_questiontext, $show_blocktitle, $questions)
1985    {
1986        $ilDB = $this->db;
1987
1988        // if the selected questions are not in a continous selection, move all questions of the
1989        // questionblock at the position of the first selected question
1990        $this->moveQuestions($questions, $questions[0], 0);
1991
1992        // now save the question block
1993        $ilUser = $this->user;
1994        $next_id = $ilDB->nextId('svy_qblk');
1995        $affectedRows = $ilDB->manipulateF(
1996            "INSERT INTO svy_qblk (questionblock_id, title, show_questiontext," .
1997            " show_blocktitle, owner_fi, tstamp) VALUES (%s, %s, %s, %s, %s, %s)",
1998            array('integer','text','text','text','integer','integer'),
1999            array($next_id, $title, $show_questiontext, $show_blocktitle, $ilUser->getId(), time())
2000        );
2001        if ($affectedRows) {
2002            $questionblock_id = $next_id;
2003            foreach ($questions as $index) {
2004                if (!$this->isQuestionInAnyBlock($index)) {
2005                    $next_id = $ilDB->nextId('svy_qblk_qst');	// #22018
2006                    $affectedRows = $ilDB->manipulateF(
2007                        "INSERT INTO svy_qblk_qst (qblk_qst_id, survey_fi, questionblock_fi, " .
2008                        "question_fi) VALUES (%s, %s, %s, %s)",
2009                        array('integer', 'integer', 'integer', 'integer'),
2010                        array($next_id, $this->getSurveyId(), $questionblock_id, $index)
2011                    );
2012                    $this->deleteConstraints($index);
2013                }
2014            }
2015        }
2016    }
2017
2018    /**
2019    * Modifies a question block
2020    *
2021    * @param integer $questionblock_id The database id of the question block
2022    * @param string $title The title of the question block
2023    * @access public
2024    */
2025    public function modifyQuestionblock($questionblock_id, $title, $show_questiontext, $show_blocktitle)
2026    {
2027        $ilDB = $this->db;
2028        $affectedRows = $ilDB->manipulateF(
2029            "UPDATE svy_qblk SET title = %s, show_questiontext = %s," .
2030            " show_blocktitle = %s WHERE questionblock_id = %s",
2031            array('text','text','text','integer'),
2032            array($title, $show_questiontext, $show_blocktitle, $questionblock_id)
2033        );
2034    }
2035
2036    /**
2037    * Deletes the constraints for a question
2038    *
2039    * @param integer $question_id The database id of the question
2040    * @access public
2041    */
2042    public function deleteConstraints($question_id)
2043    {
2044        $ilDB = $this->db;
2045        $result = $ilDB->queryF(
2046            "SELECT constraint_fi FROM svy_qst_constraint WHERE question_fi = %s AND survey_fi = %s",
2047            array('integer','integer'),
2048            array($question_id, $this->getSurveyId())
2049        );
2050        $constraints = array();
2051        while ($row = $ilDB->fetchAssoc($result)) {
2052            array_push($constraints, $row["constraint_fi"]);
2053        }
2054        foreach ($constraints as $constraint_id) {
2055            $this->deleteConstraint($constraint_id);
2056        }
2057    }
2058
2059    /**
2060    * Deletes a constraint of a question
2061    *
2062    * @param integer $constraint_id The database id of the constraint
2063    * @param integer $question_id The database id of the question
2064    * @access public
2065    */
2066    public function deleteConstraint($constraint_id)
2067    {
2068        $ilDB = $this->db;
2069        $affectedRows = $ilDB->manipulateF(
2070            "DELETE FROM svy_constraint WHERE constraint_id = %s",
2071            array('integer'),
2072            array($constraint_id)
2073        );
2074        $affectedRows = $ilDB->manipulateF(
2075            "DELETE FROM svy_qst_constraint WHERE constraint_fi = %s",
2076            array('integer'),
2077            array($constraint_id)
2078        );
2079    }
2080
2081    /**
2082    * Returns the survey questions and questionblocks in an array
2083    *
2084    * @access public
2085    */
2086    public function &getSurveyQuestions($with_answers = false)
2087    {
2088        $ilDB = $this->db;
2089        // get questionblocks
2090        $all_questions = array();
2091        $result = $ilDB->queryF(
2092            "SELECT svy_qtype.type_tag, svy_qtype.plugin, svy_question.question_id, " .
2093            "svy_svy_qst.heading FROM svy_qtype, svy_question, svy_svy_qst WHERE svy_svy_qst.survey_fi = %s AND " .
2094            "svy_svy_qst.question_fi = svy_question.question_id AND svy_question.questiontype_fi = svy_qtype.questiontype_id " .
2095            "ORDER BY svy_svy_qst.sequence",
2096            array('integer'),
2097            array($this->getSurveyId())
2098        );
2099        while ($row = $ilDB->fetchAssoc($result)) {
2100            $add = true;
2101            if ($row["plugin"]) {
2102                if (!$this->isPluginActive($row["type_tag"])) {
2103                    $add = false;
2104                }
2105            }
2106            if ($add) {
2107                $question = self::_instanciateQuestion($row["question_id"]);
2108                $questionrow = $question->getQuestionDataArray($row["question_id"]);
2109                foreach ($row as $key => $value) {
2110                    $questionrow[$key] = $value;
2111                }
2112                $all_questions[$row["question_id"]] = $questionrow;
2113                $all_questions[$row["question_id"]]["usableForPrecondition"] = $question->usableForPrecondition();
2114                $all_questions[$row["question_id"]]["availableRelations"] = $question->getAvailableRelations();
2115            }
2116        }
2117        // get all questionblocks
2118        $questionblocks = array();
2119        if (count($all_questions)) {
2120            $result = $ilDB->queryF(
2121                "SELECT svy_qblk.*, svy_qblk_qst.question_fi FROM svy_qblk, svy_qblk_qst WHERE " .
2122                "svy_qblk.questionblock_id = svy_qblk_qst.questionblock_fi AND svy_qblk_qst.survey_fi = %s " .
2123                "AND " . $ilDB->in('svy_qblk_qst.question_fi', array_keys($all_questions), false, 'integer'),
2124                array('integer'),
2125                array($this->getSurveyId())
2126            );
2127            while ($row = $ilDB->fetchAssoc($result)) {
2128                $questionblocks[$row['question_fi']] = $row;
2129            }
2130        }
2131
2132        foreach ($all_questions as $question_id => $row) {
2133            $constraints = $this->getConstraints($question_id);
2134            if (isset($questionblocks[$question_id])) {
2135                $all_questions[$question_id]["questionblock_title"] = $questionblocks[$question_id]['title'];
2136                $all_questions[$question_id]["questionblock_id"] = $questionblocks[$question_id]['questionblock_id'];
2137                $all_questions[$question_id]["constraints"] = $constraints;
2138            } else {
2139                $all_questions[$question_id]["questionblock_title"] = "";
2140                $all_questions[$question_id]["questionblock_id"] = "";
2141                $all_questions[$question_id]["constraints"] = $constraints;
2142            }
2143            if ($with_answers) {
2144                $answers = array();
2145                $result = $ilDB->queryF(
2146                    "SELECT svy_variable.*, svy_category.title FROM svy_variable, svy_category " .
2147                    "WHERE svy_variable.question_fi = %s AND svy_variable.category_fi = svy_category.category_id " .
2148                    "ORDER BY sequence ASC",
2149                    array('integer'),
2150                    array($question_id)
2151                );
2152                if ($result->numRows() > 0) {
2153                    while ($data = $ilDB->fetchAssoc($result)) {
2154                        array_push($answers, $data["title"]);
2155                    }
2156                }
2157                $all_questions[$question_id]["answers"] = $answers;
2158            }
2159        }
2160        return $all_questions;
2161    }
2162
2163    /**
2164    * Sets the obligatory states for questions in a survey from the questions form
2165    *
2166    * @param array $obligatory_questions The questions which should be set obligatory from the questions form, the remaining questions should be setted not obligatory
2167    * @access public
2168    */
2169    public function setObligatoryStates($obligatory_questions)
2170    {
2171        $ilDB = $this->db;
2172        $result = $ilDB->queryF(
2173            "SELECT * FROM svy_svy_qst WHERE survey_fi = %s",
2174            array('integer'),
2175            array($this->getSurveyId())
2176        );
2177        if ($result->numRows()) {
2178            while ($row = $ilDB->fetchAssoc($result)) {
2179                if (!array_key_exists($row["question_fi"], $obligatory_questions)) {
2180                    $obligatory_questions[$row["question_fi"]] = 0;
2181                }
2182            }
2183        }
2184
2185        // set the obligatory states in the database
2186        foreach ($obligatory_questions as $question_fi => $obligatory) {
2187            // #12420
2188            $ilDB->manipulate("UPDATE svy_question" .
2189                " SET obligatory = " . $ilDB->quote($obligatory, "integer") .
2190                " WHERE question_id = " . $ilDB->quote($question_fi, "integer"));
2191        }
2192    }
2193
2194    /**
2195    * Returns the survey pages in an array (a page contains one or more questions)
2196    *
2197    * @access public
2198    */
2199    public function &getSurveyPages()
2200    {
2201        $ilDB = $this->db;
2202        // get questionblocks
2203        $all_questions = array();
2204        $result = $ilDB->queryF(
2205            "SELECT svy_question.*, svy_qtype.type_tag, svy_svy_qst.heading FROM " .
2206            "svy_question, svy_qtype, svy_svy_qst WHERE svy_svy_qst.survey_fi = %s AND " .
2207            "svy_svy_qst.question_fi = svy_question.question_id AND svy_question.questiontype_fi = svy_qtype.questiontype_id " .
2208            "ORDER BY svy_svy_qst.sequence",
2209            array('integer'),
2210            array($this->getSurveyId())
2211        );
2212        while ($row = $ilDB->fetchAssoc($result)) {
2213            $all_questions[$row["question_id"]] = $row;
2214        }
2215        // get all questionblocks
2216        $questionblocks = array();
2217        if (count($all_questions)) {
2218            $result = $ilDB->queryF(
2219                "SELECT svy_qblk.*, svy_qblk_qst.question_fi FROM svy_qblk, svy_qblk_qst " .
2220                "WHERE svy_qblk.questionblock_id = svy_qblk_qst.questionblock_fi AND svy_qblk_qst.survey_fi = %s " .
2221                "AND " . $ilDB->in('svy_qblk_qst.question_fi', array_keys($all_questions), false, 'integer'),
2222                array('integer'),
2223                array($this->getSurveyId())
2224            );
2225            while ($row = $ilDB->fetchAssoc($result)) {
2226                $questionblocks[$row['question_fi']] = $row;
2227            }
2228        }
2229
2230        $all_pages = array();
2231        $pageindex = -1;
2232        $currentblock = "";
2233        foreach ($all_questions as $question_id => $row) {
2234            $constraints = array();
2235            if (isset($questionblocks[$question_id])) {
2236                if (!$currentblock or ($currentblock != $questionblocks[$question_id]['questionblock_id'])) {
2237                    $pageindex++;
2238                }
2239                $all_questions[$question_id]['page'] = $pageindex;
2240                $all_questions[$question_id]["questionblock_title"] = $questionblocks[$question_id]['title'];
2241                $all_questions[$question_id]["questionblock_id"] = $questionblocks[$question_id]['questionblock_id'];
2242                $all_questions[$question_id]["questionblock_show_questiontext"] = $questionblocks[$question_id]['show_questiontext'];
2243                $all_questions[$question_id]["questionblock_show_blocktitle"] = $questionblocks[$question_id]['show_blocktitle'];
2244                $currentblock = $questionblocks[$question_id]['questionblock_id'];
2245                $constraints = $this->getConstraints($question_id);
2246                $all_questions[$question_id]["constraints"] = $constraints;
2247            } else {
2248                $pageindex++;
2249                $all_questions[$question_id]['page'] = $pageindex;
2250                $all_questions[$question_id]["questionblock_title"] = "";
2251                $all_questions[$question_id]["questionblock_id"] = "";
2252                $all_questions[$question_id]["questionblock_show_questiontext"] = 1;
2253                $all_questions[$question_id]["questionblock_show_blocktitle"] = 1;
2254                $currentblock = "";
2255                $constraints = $this->getConstraints($question_id);
2256                $all_questions[$question_id]["constraints"] = $constraints;
2257            }
2258            if (!isset($all_pages[$pageindex])) {
2259                $all_pages[$pageindex] = array();
2260            }
2261            array_push($all_pages[$pageindex], $all_questions[$question_id]);
2262        }
2263        // calculate position percentage for every page
2264        $max = count($all_pages);
2265        $counter = 1;
2266        foreach ($all_pages as $index => $block) {
2267            foreach ($block as $blockindex => $question) {
2268                $all_pages[$index][$blockindex]["position"] = $counter / $max;
2269            }
2270            $counter++;
2271        }
2272
2273        return $all_pages;
2274    }
2275
2276    /**
2277    * Returns the next "page" of a running test
2278    *
2279    * @param integer $active_page_question_id The database id of one of the questions on that page
2280    * @param integer $direction The direction of the next page (-1 = previous page, 1 = next page)
2281    * @return mixed An array containing the question id's of the questions on the next page if there is a next page, 0 if the next page is before the start page, 1 if the next page is after the last page
2282    * @access public
2283    */
2284    public function getNextPage($active_page_question_id, $direction)
2285    {
2286        $foundpage = -1;
2287        $pages = &$this->getSurveyPages();
2288        if (strcmp($active_page_question_id, "") == 0) {
2289            return $pages[0];
2290        }
2291        foreach ($pages as $key => $question_array) {
2292            foreach ($question_array as $question) {
2293                if ($active_page_question_id == $question["question_id"]) {
2294                    $foundpage = $key;
2295                }
2296            }
2297        }
2298        if ($foundpage == -1) {
2299            // error: page not found
2300        } else {
2301            $foundpage += $direction;
2302            if ($foundpage < 0) {
2303                return 0;
2304            }
2305            if ($foundpage >= count($pages)) {
2306                return 1;
2307            }
2308            return $pages[$foundpage];
2309        }
2310    }
2311
2312    /**
2313    * Returns the available question pools for the active user
2314    *
2315    * @return array The available question pools
2316    * @access public
2317    */
2318    public function &getAvailableQuestionpools($use_obj_id = false, $could_be_offline = false, $showPath = false, $permission = "read")
2319    {
2320        return ilObjSurveyQuestionPool::_getAvailableQuestionpools($use_obj_id, $could_be_offline, $showPath, $permission);
2321    }
2322
2323    /**
2324    * Returns a precondition with a given id
2325    *
2326    * @access public
2327    */
2328    public function getPrecondition($id)
2329    {
2330        $ilDB = $this->db;
2331
2332        $result_array = array();
2333        $result = $ilDB->queryF(
2334            "SELECT svy_constraint.*, svy_relation.*, svy_qst_constraint.question_fi ref_question_fi FROM svy_qst_constraint, svy_constraint, " .
2335            "svy_relation WHERE svy_constraint.relation_fi = svy_relation.relation_id AND " .
2336            "svy_qst_constraint.constraint_fi = svy_constraint.constraint_id AND svy_constraint.constraint_id = %s",
2337            array('integer'),
2338            array($id)
2339        );
2340        $pc = array();
2341        if ($result->numRows()) {
2342            $pc = $ilDB->fetchAssoc($result);
2343        }
2344        return $pc;
2345    }
2346
2347    /**
2348    * Returns the constraints to a given question or questionblock
2349    *
2350    * @access public
2351    */
2352    public function getConstraints($question_id)
2353    {
2354        $ilDB = $this->db;
2355
2356        $result_array = array();
2357        $result = $ilDB->queryF(
2358            "SELECT svy_constraint.*, svy_relation.* FROM svy_qst_constraint, svy_constraint, svy_relation " .
2359            "WHERE svy_constraint.relation_fi = svy_relation.relation_id AND " .
2360            "svy_qst_constraint.constraint_fi = svy_constraint.constraint_id AND svy_qst_constraint.question_fi = %s " .
2361            "AND svy_qst_constraint.survey_fi = %s",
2362            array('integer','integer'),
2363            array($question_id, $this->getSurveyId())
2364        );
2365        while ($row = $ilDB->fetchAssoc($result)) {
2366            $question_type = SurveyQuestion::_getQuestionType($row["question_fi"]);
2367            SurveyQuestion::_includeClass($question_type);
2368            $question = new $question_type();
2369            $question->loadFromDb($row["question_fi"]);
2370            $valueoutput = $question->getPreconditionValueOutput($row["value"]);
2371            array_push($result_array, array("id" => $row["constraint_id"], "question" => $row["question_fi"], "short" => $row["shortname"], "long" => $row["longname"], "value" => $row["value"], "conjunction" => $row["conjunction"], "valueoutput" => $valueoutput));
2372        }
2373        return $result_array;
2374    }
2375
2376    /**
2377    * Returns the constraints to a given question or questionblock
2378    *
2379    * @access public
2380    */
2381    public static function _getConstraints($survey_id)
2382    {
2383        global $DIC;
2384
2385        $ilDB = $DIC->database();
2386        $result_array = array();
2387        $result = $ilDB->queryF(
2388            "SELECT svy_qst_constraint.question_fi as for_question, svy_constraint.*, svy_relation.* " .
2389            "FROM svy_qst_constraint, svy_constraint, svy_relation WHERE svy_constraint.relation_fi = svy_relation.relation_id " .
2390            "AND svy_qst_constraint.constraint_fi = svy_constraint.constraint_id AND svy_qst_constraint.survey_fi = %s",
2391            array('integer'),
2392            array($survey_id)
2393        );
2394        while ($row = $ilDB->fetchAssoc($result)) {
2395            array_push($result_array, array("id" => $row["constraint_id"], "for_question" => $row["for_question"], "question" => $row["question_fi"], "short" => $row["shortname"], "long" => $row["longname"], "relation_id" => $row["relation_id"], "value" => $row["value"], 'conjunction' => $row['conjunction']));
2396        }
2397        return $result_array;
2398    }
2399
2400
2401    /**
2402    * Returns all variables of a question
2403    *
2404    * @access public
2405    */
2406    public function &getVariables($question_id)
2407    {
2408        $ilDB = $this->db;
2409
2410        $result_array = array();
2411        $result = $ilDB->queryF(
2412            "SELECT svy_variable.*, svy_category.title FROM svy_variable LEFT JOIN " .
2413            "svy_category ON svy_variable.category_fi = svy_category.category_id WHERE svy_variable.question_fi = %s " .
2414            "ORDER BY svy_variable.sequence",
2415            array('integer'),
2416            array($question_id)
2417        );
2418        while ($row = $ilDB->fetchObject($result)) {
2419            $result_array[$row->sequence] = $row;
2420        }
2421        return $result_array;
2422    }
2423
2424    /**
2425    * Adds a constraint
2426    *
2427    * @param integer $if_question_id The question id of the question which defines a precondition
2428    * @param integer $relation The database id of the relation
2429    * @param mixed $value The value compared with the relation
2430    * @access public
2431    */
2432    public function addConstraint($if_question_id, $relation, $value, $conjunction)
2433    {
2434        $ilDB = $this->db;
2435
2436        $next_id = $ilDB->nextId('svy_constraint');
2437        $affectedRows = $ilDB->manipulateF(
2438            "INSERT INTO svy_constraint (constraint_id, question_fi, relation_fi, value, conjunction) VALUES " .
2439            "(%s, %s, %s, %s, %s)",
2440            array('integer','integer','integer','float', 'integer'),
2441            array($next_id, $if_question_id, $relation, $value, $conjunction)
2442        );
2443        if ($affectedRows) {
2444            return $next_id;
2445        } else {
2446            return null;
2447        }
2448    }
2449
2450
2451    /**
2452    * Adds a constraint to a question
2453    *
2454    * @param integer $to_question_id The question id of the question where to add the constraint
2455    * @param integer $constraint_id The id of the constraint
2456    */
2457    public function addConstraintToQuestion($to_question_id, $constraint_id)
2458    {
2459        $ilDB = $this->db;
2460
2461        $next_id = $ilDB->nextId('svy_qst_constraint');
2462        $affectedRows = $ilDB->manipulateF(
2463            "INSERT INTO svy_qst_constraint (question_constraint_id, survey_fi, question_fi, " .
2464            "constraint_fi) VALUES (%s, %s, %s, %s)",
2465            array('integer','integer','integer','integer'),
2466            array($next_id, $this->getSurveyId(), $to_question_id, $constraint_id)
2467        );
2468    }
2469
2470    /**
2471    * Updates a precondition
2472    *
2473    * @param integer $precondition_id The id of the original precondition
2474    * @param integer $to_question_id The question id of the question where to add the constraint
2475    * @param integer $if_question_id The question id of the question which defines a precondition
2476    * @param integer $relation The database id of the relation
2477    * @param mixed $value The value compared with the relation
2478    * @access public
2479    */
2480    public function updateConstraint($precondition_id, $if_question_id, $relation, $value, $conjunction)
2481    {
2482        $ilDB = $this->db;
2483        $affectedRows = $ilDB->manipulateF(
2484            "UPDATE svy_constraint SET question_fi = %s, relation_fi = %s, value = %s, conjunction = %s " .
2485            "WHERE constraint_id = %s",
2486            array('integer','integer','float','integer','integer'),
2487            array($if_question_id, $relation, $value, $conjunction, $precondition_id)
2488        );
2489    }
2490
2491    public function updateConjunctionForQuestions($questions, $conjunction)
2492    {
2493        $ilDB = $this->db;
2494        foreach ($questions as $question_id) {
2495            $affectedRows = $ilDB->manipulateF(
2496                "UPDATE svy_constraint SET conjunction = %s " .
2497                "WHERE constraint_id IN (SELECT constraint_fi FROM svy_qst_constraint WHERE svy_qst_constraint.question_fi = %s)",
2498                array('integer','integer'),
2499                array($conjunction, $question_id)
2500            );
2501        }
2502    }
2503
2504    /**
2505    * Returns all available relations
2506    *
2507    * @access public
2508    */
2509    public function getAllRelations($short_as_key = false)
2510    {
2511        $ilDB = $this->db;
2512
2513        // #7987
2514        $custom_order = array("equal", "not_equal", "less", "less_or_equal", "more", "more_or_equal");
2515        $custom_order = array_flip($custom_order);
2516
2517        $result_array = array();
2518        $result = $ilDB->query("SELECT * FROM svy_relation");
2519        while ($row = $ilDB->fetchAssoc($result)) {
2520            if ($short_as_key) {
2521                $result_array[$row["shortname"]] = array("short" => $row["shortname"], "long" => $row["longname"], "id" => $row["relation_id"], "order" => $custom_order[$row["longname"]]);
2522            } else {
2523                $result_array[$row["relation_id"]] = array("short" => $row["shortname"], "long" => $row["longname"], "order" => $custom_order[$row["longname"]]);
2524            }
2525        }
2526
2527        $result_array = ilUtil::sortArray($result_array, "order", "ASC", true, true);
2528        foreach ($result_array as $idx => $item) {
2529            unset($result_array[$idx]["order"]);
2530        }
2531
2532        return $result_array;
2533    }
2534
2535
2536
2537
2538    /**
2539    * Deletes the working data of a question in the database
2540    *
2541    * @param integer $question_id The database id of the question
2542    * @param integer $active_id The active id of the user who worked through the question
2543    * @access public
2544    */
2545    public function deleteWorkingData($question_id, $active_id)
2546    {
2547        $ilDB = $this->db;
2548
2549        $affectedRows = $ilDB->manipulateF(
2550            "DELETE FROM svy_answer WHERE question_fi = %s AND active_fi = %s",
2551            array('integer','integer'),
2552            array($question_id, $active_id)
2553        );
2554    }
2555
2556    /**
2557    * Gets the working data of question from the database
2558    *
2559    * @param integer $question_id The database id of the question
2560    * @param integer $active_id The active id of the user who worked through the question
2561    * @return array The resulting database dataset as an array
2562    * @access public
2563    */
2564    public function loadWorkingData($question_id, $active_id)
2565    {
2566        $ilDB = $this->db;
2567        $result_array = array();
2568        $result = $ilDB->queryF(
2569            "SELECT * FROM svy_answer WHERE question_fi = %s AND active_fi = %s",
2570            array('integer','integer'),
2571            array($question_id, $active_id)
2572        );
2573        if ($result->numRows() >= 1) {
2574            while ($row = $ilDB->fetchAssoc($result)) {
2575                array_push($result_array, $row);
2576            }
2577            return $result_array;
2578        } else {
2579            return $result_array;
2580        }
2581    }
2582
2583    /**
2584    * Starts the survey creating an entry in the database
2585    *
2586    * @param integer $user_id The database id of the user who starts the survey
2587    * @access public
2588    */
2589    public function startSurvey($user_id, $anonymous_id, $appraisee_id)
2590    {
2591        $ilDB = $this->db;
2592
2593        if ($this->getAnonymize() && (strlen($anonymous_id) == 0)) {
2594            return;
2595        }
2596
2597        if (strcmp($user_id, "") == 0) {
2598            if ($user_id == ANONYMOUS_USER_ID) {
2599                $user_id = 0;
2600            }
2601        }
2602        $next_id = $ilDB->nextId('svy_finished');
2603        $affectedRows = $ilDB->manipulateF(
2604            "INSERT INTO svy_finished (finished_id, survey_fi, user_fi, anonymous_id, state, tstamp, appr_id) " .
2605            "VALUES (%s, %s, %s, %s, %s, %s, %s)",
2606            array('integer','integer','integer','text','text','integer','integer'),
2607            array($next_id, $this->getSurveyId(), $user_id, $anonymous_id, 0, time(), $appraisee_id)
2608        );
2609        return $next_id;
2610    }
2611
2612    /**
2613    * Finishes the survey creating an entry in the database
2614    *
2615    * @param integer $user_id The database id of the user who finishes the survey
2616    * @access public
2617    */
2618    public function finishSurvey($finished_id)
2619    {
2620        $ilDB = $this->db;
2621
2622        $ilDB->manipulateF(
2623            "UPDATE svy_finished SET state = %s, tstamp = %s" .
2624            " WHERE survey_fi = %s AND finished_id = %s",
2625            array('text','integer','integer','integer'),
2626            array(1, time(), $this->getSurveyId(), $finished_id)
2627        );
2628
2629        // self eval writes skills on finishing
2630        if ($this->getMode() == ilObjSurvey::MODE_SELF_EVAL) {
2631            $user = $this->getUserDataFromActiveId($finished_id);
2632            $sskill = new ilSurveySkill($this);
2633            $sskill->writeAndAddSelfEvalSkills($user['usr_id']);
2634        }
2635
2636        $this->checkTutorNotification();
2637    }
2638
2639    /**
2640    * Sets the number of the active survey page
2641    *
2642    * @param integer $finished_id The database id of the active user
2643    * @param integer $page_id The index of the page
2644    * @access public
2645    */
2646    public function setPage($finished_id, $page_id)
2647    {
2648        $ilDB = $this->db;
2649
2650        $affectedRows = $ilDB->manipulateF(
2651            "UPDATE svy_finished SET lastpage = %s WHERE finished_id = %s",
2652            array('integer','integer'),
2653            array(($page_id) ? $page_id : 0, $finished_id)
2654        );
2655    }
2656
2657    /**
2658     * @param $a_user_id user who did the survey
2659     * @param $a_anonymize_id
2660     * @param $a_appr_id
2661     */
2662    public function sendNotificationMail($a_user_id, $a_anonymize_id, $a_appr_id)
2663    {
2664        // #12755
2665        $placeholders = array(
2666            "FIRST_NAME" => "firstname",
2667            "LAST_NAME" => "lastname",
2668            "LOGIN" => "login",
2669            // old style
2670            "firstname" => "firstname"
2671        );
2672
2673        //mailaddresses is just text split by commas.
2674        //sendMail can send emails if it gets an user id or an email as first parameter.
2675        $recipients = preg_split('/,/', $this->mailaddresses);
2676        foreach ($recipients as $recipient) {
2677            // #11298
2678            $ntf = new ilSystemNotification();
2679            $ntf->setLangModules(array("survey"));
2680            $ntf->setRefId($this->getRefId());
2681            $ntf->setSubjectLangId('finished_mail_subject');
2682
2683            $messagetext = $this->mailparticipantdata;
2684            if (trim($messagetext)) {
2685                if (!$this->hasAnonymizedResults()) {
2686                    $data = ilObjUser::_getUserData(array($a_user_id));
2687                    $data = $data[0];
2688                }
2689                foreach ($placeholders as $key => $mapping) {
2690                    if ($this->hasAnonymizedResults()) { // #16480
2691                        $messagetext = str_replace('[' . $key . ']', '', $messagetext);
2692                    } else {
2693                        $messagetext = str_replace('[' . $key . ']', trim($data[$mapping]), $messagetext);
2694                    }
2695                }
2696                $ntf->setIntroductionDirect($messagetext);
2697            } else {
2698                $ntf->setIntroductionLangId('survey_notification_finished_introduction');
2699            }
2700
2701            // 360°? add appraisee data
2702            if ($a_appr_id) {
2703                $ntf->addAdditionalInfo(
2704                    'survey_360_appraisee',
2705                    ilUserUtil::getNamePresentation($a_appr_id)
2706                );
2707            }
2708
2709            $active_id = $this->getActiveID($a_user_id, $a_anonymize_id, $a_appr_id);
2710            $ntf->addAdditionalInfo(
2711                'results',
2712                $this->getParticipantTextResults($active_id),
2713                true
2714            );
2715
2716            $ntf->setGotoLangId('survey_notification_tutor_link');
2717            $ntf->setReasonLangId('survey_notification_finished_reason');
2718
2719            if (is_numeric($recipient)) {
2720                $lng = $ntf->getUserLanguage($recipient);
2721                $ntf->sendMail(array($recipient), null, null);
2722            } else {
2723                $recipient = trim($recipient);
2724                $user_ids = ilObjUser::getUserIdsByEmail($recipient);
2725                if (empty($user_ids)) {
2726                    $ntf->sendMail(array($recipient), null, null);
2727                } else {
2728                    foreach ($user_ids as $user_id) {
2729                        $lng = $ntf->getUserLanguage($user_id);
2730                        $ntf->sendMail(array($user_id), null, null);
2731                    }
2732                }
2733            }
2734        }
2735    }
2736
2737    protected function getParticipantTextResults($active_id)
2738    {
2739        $textresult = "";
2740        $userResults = &$this->getUserSpecificResults(array($active_id));
2741        $questions = &$this->getSurveyQuestions(true);
2742        $questioncounter = 1;
2743        foreach ($questions as $question_id => $question_data) {
2744            $textresult .= $questioncounter++ . ". " . $question_data["title"] . "\n";
2745            $found = $userResults[$question_id][$active_id];
2746            $text = "";
2747            if (is_array($found)) {
2748                $text = implode("\n", $found);
2749            } else {
2750                $text = $found;
2751            }
2752            if (strlen($text) == 0) {
2753                $text = self::getSurveySkippedValue();
2754            }
2755            $text = str_replace("<br />", "\n", $text);
2756            $textresult .= $text . "\n\n";
2757        }
2758        return $textresult;
2759    }
2760
2761    /**
2762    * Checks if a user already started a survey
2763    *
2764    * @param integer $user_id The database id of the user
2765    * @return mixed false, if the user has not started the survey, 0 if the user has started the survey but not finished it, 1 if the user has finished the survey
2766    * @access public
2767    */
2768    public function isSurveyStarted($user_id, $anonymize_id, $appr_id = 0)
2769    {
2770        $ilDB = $this->db;
2771
2772        // #15031 - should not matter if code was used by registered or anonymous (each code must be unique)
2773        if ($anonymize_id) {
2774            $result = $ilDB->queryF(
2775                "SELECT * FROM svy_finished" .
2776                " WHERE survey_fi = %s AND anonymous_id = %s AND appr_id = %s",
2777                array('integer','text','integer'),
2778                array($this->getSurveyId(), $anonymize_id, $appr_id)
2779            );
2780        } else {
2781            $result = $ilDB->queryF(
2782                "SELECT * FROM svy_finished" .
2783                " WHERE survey_fi = %s AND user_fi = %s AND appr_id = %s",
2784                array('integer','integer','integer'),
2785                array($this->getSurveyId(), $user_id, $appr_id)
2786            );
2787        }
2788        if ($result->numRows() == 0) {
2789            return false;
2790        } else {
2791            $row = $ilDB->fetchAssoc($result);
2792            // yes, we are doing it this way
2793            $_SESSION["finished_id"][$this->getId()] = $row["finished_id"];
2794
2795            return (int) $row["state"];
2796        }
2797    }
2798
2799    /**
2800    * Checks if a user already started a survey
2801    *
2802    * @param integer $user_id The database id of the user
2803    * @return mixed false, if the user has not started the survey, 0 if the user has started the survey but not finished it, 1 if the user has finished the survey
2804    * @access public
2805    */
2806    public function getActiveID($user_id, $anonymize_id, $appr_id)
2807    {
2808        $ilDB = $this->db;
2809
2810        // see self::isSurveyStarted()
2811
2812        // #15031 - should not matter if code was used by registered or anonymous (each code must be unique)
2813        if ($anonymize_id) {
2814            $result = $ilDB->queryF(
2815                "SELECT finished_id FROM svy_finished" .
2816                " WHERE survey_fi = %s AND anonymous_id = %s AND appr_id = %s",
2817                array('integer','text','integer'),
2818                array($this->getSurveyId(), $anonymize_id, $appr_id)
2819            );
2820        } else {
2821            $result = $ilDB->queryF(
2822                "SELECT finished_id FROM svy_finished" .
2823                " WHERE survey_fi = %s AND user_fi = %s AND appr_id = %s",
2824                array('integer','integer','integer'),
2825                array($this->getSurveyId(), $user_id, $appr_id)
2826            );
2827        }
2828        if ($result->numRows() == 0) {
2829            return false;
2830        } else {
2831            $row = $ilDB->fetchAssoc($result);
2832            return $row["finished_id"];
2833        }
2834    }
2835
2836    /**
2837    * Returns the question id of the last active page a user visited in a survey
2838    *
2839    * @param integer $active_id The active id of the user
2840    * @return mixed Empty string if the user has not worked through a page, question id of the last page otherwise
2841    * @access public
2842    */
2843    public function getLastActivePage($active_id)
2844    {
2845        $ilDB = $this->db;
2846        $result = $ilDB->queryF(
2847            "SELECT lastpage FROM svy_finished WHERE finished_id = %s",
2848            array('integer'),
2849            array($active_id)
2850        );
2851        if ($result->numRows() == 0) {
2852            return "";
2853        } else {
2854            $row = $ilDB->fetchAssoc($result);
2855            return ($row["lastpage"]) ? $row["lastpage"] : '';
2856        }
2857    }
2858
2859    /**
2860    * Checks if a constraint is valid
2861    *
2862    * @param array $constraint_data The database row containing the constraint data
2863    * @param array $working_data The user input of the related question
2864    * @return boolean true if the constraint is valid, otherwise false
2865    * @access public
2866    */
2867    public function checkConstraint($constraint_data, $working_data)
2868    {
2869        if (!is_array($working_data) || count($working_data) == 0) {
2870            return 0;
2871        }
2872
2873        if ((count($working_data) == 1) and (strcmp($working_data[0]["value"], "") == 0)) {
2874            return 0;
2875        }
2876
2877        $found = false;
2878        foreach ($working_data as $data) {
2879            switch ($constraint_data["short"]) {
2880                case "<":
2881                    if ($data["value"] < $constraint_data["value"]) {
2882                        $found = true;
2883                    }
2884                    break;
2885
2886                case "<=":
2887                    if ($data["value"] <= $constraint_data["value"]) {
2888                        $found = true;
2889                    }
2890                    break;
2891
2892                case "=":
2893                    if ($data["value"] == $constraint_data["value"]) {
2894                        $found = true;
2895                    }
2896                    break;
2897
2898                case "<>":
2899                    if ($data["value"] <> $constraint_data["value"]) {
2900                        $found = true;
2901                    }
2902                    break;
2903
2904                case ">=":
2905                    if ($data["value"] >= $constraint_data["value"]) {
2906                        $found = true;
2907                    }
2908                    break;
2909
2910                case ">":
2911                    if ($data["value"] > $constraint_data["value"]) {
2912                        $found = true;
2913                    }
2914                    break;
2915            }
2916            if ($found) {
2917                break;
2918            }
2919        }
2920
2921        return (int) $found;
2922    }
2923
2924    public static function _hasDatasets($survey_id)
2925    {
2926        global $DIC;
2927
2928        $ilDB = $DIC->database();
2929
2930        $result = $ilDB->queryF(
2931            "SELECT finished_id FROM svy_finished WHERE survey_fi = %s",
2932            array('integer'),
2933            array($survey_id)
2934        );
2935        return ($result->numRows()) ? true : false;
2936    }
2937
2938    /**
2939    * Get the finished id's of all survey participants
2940    *
2941    * @return array An array containing finished_id's of all survey participants
2942    * @access public
2943    */
2944    public function &getSurveyFinishedIds()
2945    {
2946        $ilDB = $this->db;
2947        $ilLog = $this->log;
2948
2949        $users = array();
2950        $result = $ilDB->queryF(
2951            "SELECT * FROM svy_finished WHERE survey_fi = %s",
2952            array('integer'),
2953            array($this->getSurveyId())
2954        );
2955        if ($result->numRows()) {
2956            while ($row = $ilDB->fetchAssoc($result)) {
2957                array_push($users, $row["finished_id"]);
2958            }
2959        }
2960        return $users;
2961    }
2962
2963    /**
2964    * Calculates the evaluation data for the user specific results
2965    *
2966    * @return array An array containing the user specific results
2967    * @access public
2968    */
2969    public function getUserSpecificResults($finished_ids)
2970    {
2971        $evaluation = array();
2972
2973        foreach (array_keys($this->getSurveyQuestions()) as $question_id) {
2974            // get question instance
2975            $question_type = SurveyQuestion::_getQuestionType($question_id);
2976            SurveyQuestion::_includeClass($question_type);
2977            $question = new $question_type();
2978            $question->loadFromDb($question_id);
2979
2980            $q_eval = SurveyQuestion::_instanciateQuestionEvaluation($question_id, $finished_ids);
2981            $q_res = $q_eval->getResults();
2982
2983            $data = array();
2984            foreach ($finished_ids as $user_id) {
2985                $data[$user_id] = $q_eval->parseUserSpecificResults($q_res, $user_id);
2986            }
2987
2988            $evaluation[$question_id] = $data;
2989        }
2990
2991        return $evaluation;
2992    }
2993
2994    /**
2995    * Returns the user information from an active_id (survey_finished.finished_id)
2996    *
2997    * @param integer $active_id The active id of the user
2998    * @return array An array containing the user data
2999    * @access public
3000    */
3001    public function getUserDataFromActiveId($active_id, $force_non_anonymous = false)
3002    {
3003        $ilDB = $this->db;
3004
3005        $surveySetting = new ilSetting("survey");
3006        $use_anonymous_id = $surveySetting->get("use_anonymous_id");
3007        $result = $ilDB->queryF(
3008            "SELECT * FROM svy_finished WHERE finished_id = %s",
3009            array('integer'),
3010            array($active_id)
3011        );
3012        $row = array();
3013        $foundrows = $result->numRows();
3014        if ($foundrows) {
3015            $row = $ilDB->fetchAssoc($result);
3016        }
3017        $name = ($use_anonymous_id) ? $row["anonymous_id"] : $this->lng->txt("anonymous");
3018        $userdata = array(
3019            "fullname" => $name,
3020            "sortname" => $name,
3021            "firstname" => "",
3022            "lastname" => "",
3023            "login" => "",
3024            "gender" => "",
3025            "active_id" => "$active_id"
3026        );
3027        if ($foundrows) {
3028            if (($row["user_fi"] > 0) &&
3029                    (($row["user_fi"] != ANONYMOUS_USER_ID &&
3030                        !$this->hasAnonymizedResults() &&
3031                        !$this->get360Mode()) ||  // 360° uses ANONYMIZE_CODE_ALL which is wrong - see ilObjSurveyGUI::afterSave()
3032                    (bool) $force_non_anonymous)) {
3033                if (strlen(ilObjUser::_lookupLogin($row["user_fi"])) == 0) {
3034                    $userdata["fullname"] = $userdata["sortname"] = $this->lng->txt("deleted_user");
3035                } else {
3036                    $user = new ilObjUser($row["user_fi"]);
3037                    $userdata['usr_id'] = $row['user_fi'];
3038                    $userdata["fullname"] = $user->getFullname();
3039                    $gender = $user->getGender();
3040                    if (strlen($gender) == 1) {
3041                        $gender = $this->lng->txt("gender_$gender");
3042                    }
3043                    $userdata["gender"] = $gender;
3044                    $userdata["firstname"] = $user->getFirstname();
3045                    $userdata["lastname"] = $user->getLastname();
3046                    $userdata["sortname"] = $user->getLastname() . ", " . $user->getFirstname();
3047                    $userdata["login"] = $user->getLogin();
3048                }
3049            }
3050        }
3051        return $userdata;
3052    }
3053
3054    /**
3055    * Calculates the evaluation data for a given user or anonymous id
3056    *
3057    * @param array $questions An array containing all relevant information on the survey's questions
3058    * @param integer $user_id The database id of the user
3059    * @param string $anonymous_id The unique anonymous id for an anonymous survey
3060    * @return array An array containing the evaluation parameters for the user
3061    * @access public
3062    */
3063    public function &getEvaluationByUser($questions, $active_id)
3064    {
3065        $ilDB = $this->db;
3066
3067        // collect all answers
3068        $answers = array();
3069        $result = $ilDB->queryF(
3070            "SELECT * FROM svy_answer WHERE active_fi = %s",
3071            array('integer'),
3072            array($active_id)
3073        );
3074        while ($row = $ilDB->fetchAssoc($result)) {
3075            if (!is_array($answers[$row["question_fi"]])) {
3076                $answers[$row["question_fi"]] = array();
3077            }
3078            array_push($answers[$row["question_fi"]], $row);
3079        }
3080        $userdata = $this->getUserDataFromActiveId($active_id);
3081        $resultset = array(
3082            "name" => $userdata["fullname"],
3083            "firstname" => $userdata["firstname"],
3084            "lastname" => $userdata["lastname"],
3085            "login" => $userdata["login"],
3086            "gender" => $userdata["gender"],
3087            "answers" => array()
3088        );
3089        foreach ($questions as $key => $question) {
3090            if (array_key_exists($key, $answers)) {
3091                $resultset["answers"][$key] = $answers[$key];
3092            } else {
3093                $resultset["answers"][$key] = array();
3094            }
3095            sort($resultset["answers"][$key]);
3096        }
3097        return $resultset;
3098    }
3099
3100    /**
3101    * Calculates the data for the output of the question browser
3102    *
3103    * @access public
3104    */
3105    public function getQuestionsTable($arrFilter)
3106    {
3107        $ilUser = $this->user;
3108        $ilDB = $this->db;
3109        $where = "";
3110        if (is_array($arrFilter)) {
3111            if (array_key_exists('title', $arrFilter) && strlen($arrFilter['title'])) {
3112                $where .= " AND " . $ilDB->like('svy_question.title', 'text', "%%" . $arrFilter['title'] . "%%");
3113            }
3114            if (array_key_exists('description', $arrFilter) && strlen($arrFilter['description'])) {
3115                $where .= " AND " . $ilDB->like('svy_question.description', 'text', "%%" . $arrFilter['description'] . "%%");
3116            }
3117            if (array_key_exists('author', $arrFilter) && strlen($arrFilter['author'])) {
3118                $where .= " AND " . $ilDB->like('svy_question.author', 'text', "%%" . $arrFilter['author'] . "%%");
3119            }
3120            if (array_key_exists('type', $arrFilter) && strlen($arrFilter['type'])) {
3121                $where .= " AND svy_qtype.type_tag = " . $ilDB->quote($arrFilter['type'], 'text');
3122            }
3123            if (array_key_exists('spl', $arrFilter) && strlen($arrFilter['spl'])) {
3124                $where .= " AND svy_question.obj_fi = " . $ilDB->quote($arrFilter['spl'], 'integer');
3125            }
3126        }
3127
3128        $spls = &$this->getAvailableQuestionpools($use_obj_id = true, $could_be_offline = false, $showPath = false);
3129        $forbidden = "";
3130        $forbidden = " AND " . $ilDB->in('svy_question.obj_fi', array_keys($spls), false, 'integer');
3131        $forbidden .= " AND svy_question.complete = " . $ilDB->quote("1", 'text');
3132        $existing = "";
3133        $existing_questions = &$this->getExistingQuestions();
3134        if (count($existing_questions)) {
3135            $existing = " AND " . $ilDB->in('svy_question.question_id', $existing_questions, true, 'integer');
3136        }
3137
3138        $trans = ilObjSurveyQuestionPool::_getQuestionTypeTranslations();
3139
3140        $query_result = $ilDB->query("SELECT svy_question.*, svy_qtype.type_tag, svy_qtype.plugin, object_reference.ref_id" .
3141            " FROM svy_question, svy_qtype, object_reference" .
3142            " WHERE svy_question.original_id IS NULL" . $forbidden . $existing .
3143            " AND svy_question.obj_fi = object_reference.obj_id AND svy_question.tstamp > 0" .
3144            " AND svy_question.questiontype_fi = svy_qtype.questiontype_id " . $where);
3145
3146        $rows = array();
3147        if ($query_result->numRows()) {
3148            while ($row = $ilDB->fetchAssoc($query_result)) {
3149                if (array_key_exists('spl_txt', $arrFilter) && strlen($arrFilter['spl_txt'])) {
3150                    if (!stristr($spls[$row["obj_fi"]], $arrFilter['spl_txt'])) {
3151                        continue;
3152                    }
3153                }
3154
3155                $row['ttype'] = $trans[$row['type_tag']];
3156                if ($row["plugin"]) {
3157                    if ($this->isPluginActive($row["type_tag"])) {
3158                        array_push($rows, $row);
3159                    }
3160                } else {
3161                    array_push($rows, $row);
3162                }
3163            }
3164        }
3165        return $rows;
3166    }
3167
3168    /**
3169    * Calculates the data for the output of the questionblock browser
3170    *
3171    * @access public
3172    */
3173    public function getQuestionblocksTable($arrFilter)
3174    {
3175        $ilUser = $this->user;
3176        $ilDB = $this->db;
3177
3178        $where = "";
3179        if (is_array($arrFilter)) {
3180            if (array_key_exists('title', $arrFilter) && strlen($arrFilter['title'])) {
3181                $where .= " AND " . $ilDB->like('svy_qblk.title', 'text', "%%" . $arrFilter['title'] . "%%");
3182            }
3183        }
3184
3185        $query_result = $ilDB->query("SELECT svy_qblk.*, svy_svy.obj_fi FROM svy_qblk , svy_qblk_qst, svy_svy WHERE " .
3186            "svy_qblk.questionblock_id = svy_qblk_qst.questionblock_fi AND svy_svy.survey_id = svy_qblk_qst.survey_fi " .
3187            "$where GROUP BY svy_qblk.questionblock_id, svy_qblk.title, svy_qblk.show_questiontext,  svy_qblk.show_blocktitle, " .
3188            "svy_qblk.owner_fi, svy_qblk.tstamp, svy_svy.obj_fi");
3189        $rows = array();
3190        if ($query_result->numRows()) {
3191            $survey_ref_ids = ilUtil::_getObjectsByOperations("svy", "write");
3192            $surveytitles = array();
3193            foreach ($survey_ref_ids as $survey_ref_id) {
3194                $survey_id = ilObject::_lookupObjId($survey_ref_id);
3195                $surveytitles[$survey_id] = ilObject::_lookupTitle($survey_id);
3196            }
3197            while ($row = $ilDB->fetchAssoc($query_result)) {
3198                $questions_array = &$this->getQuestionblockQuestions($row["questionblock_id"]);
3199                $counter = 1;
3200                foreach ($questions_array as $key => $value) {
3201                    $questions_array[$key] = "$counter. $value";
3202                    $counter++;
3203                }
3204                if (strlen($surveytitles[$row["obj_fi"]])) { // only questionpools which are not in trash
3205                    $rows[$row["questionblock_id"]] = array(
3206                        "questionblock_id" => $row["questionblock_id"],
3207                        "title" => $row["title"],
3208                        "svy" => $surveytitles[$row["obj_fi"]],
3209                        "contains" => join(", ", $questions_array),
3210                        "owner" => $row["owner_fi"]
3211                    );
3212                }
3213            }
3214        }
3215        return $rows;
3216    }
3217
3218    /**
3219    * Returns a QTI xml representation of the survey
3220    *
3221    * @return string The QTI xml representation of the survey
3222    * @access public
3223    */
3224    public function toXML()
3225    {
3226        $a_xml_writer = new ilXmlWriter;
3227        // set xml header
3228        $a_xml_writer->xmlHeader();
3229        $attrs = array(
3230            "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
3231            "xsi:noNamespaceSchemaLocation" => "http://www.ilias.de/download/xsd/ilias_survey_4_2.xsd"
3232        );
3233        $a_xml_writer->xmlStartTag("surveyobject", $attrs);
3234        $attrs = array(
3235            "id" => $this->getSurveyId(),
3236            "title" => $this->getTitle()
3237        );
3238        $a_xml_writer->xmlStartTag("survey", $attrs);
3239
3240        $a_xml_writer->xmlElement("description", null, $this->getDescription());
3241        $a_xml_writer->xmlElement("author", null, $this->getAuthor());
3242        $a_xml_writer->xmlStartTag("objectives");
3243        $attrs = array(
3244            "label" => "introduction"
3245        );
3246        $this->addMaterialTag($a_xml_writer, $this->getIntroduction(), true, true, $attrs);
3247        $attrs = array(
3248            "label" => "outro"
3249        );
3250        $this->addMaterialTag($a_xml_writer, $this->getOutro(), true, true, $attrs);
3251        $a_xml_writer->xmlEndTag("objectives");
3252
3253        if ($this->getAnonymize()) {
3254            $attribs = array("enabled" => "1");
3255        } else {
3256            $attribs = array("enabled" => "0");
3257        }
3258        $a_xml_writer->xmlElement("anonymisation", $attribs);
3259        $a_xml_writer->xmlStartTag("restrictions");
3260        if ($this->getAnonymize() == 2) {
3261            $attribs = array("type" => "free");
3262        } else {
3263            $attribs = array("type" => "restricted");
3264        }
3265        $a_xml_writer->xmlElement("access", $attribs);
3266        if ($this->getStartDate()) {
3267            $attrs = array("type" => "date");
3268            preg_match("/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/", $this->getStartDate(), $matches);
3269            $a_xml_writer->xmlElement("startingtime", $attrs, sprintf("%04d-%02d-%02dT%02d:%02d:00", $matches[1], $matches[2], $matches[3], $matches[4], $matches[5], $matches[6]));
3270        }
3271        if ($this->getEndDate()) {
3272            $attrs = array("type" => "date");
3273            preg_match("/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/", $this->getEndDate(), $matches);
3274            $a_xml_writer->xmlElement("endingtime", $attrs, sprintf("%04d-%02d-%02dT%02d:%02d:00", $matches[1], $matches[2], $matches[3], $matches[4], $matches[5], $matches[6]));
3275        }
3276        $a_xml_writer->xmlEndTag("restrictions");
3277
3278        // constraints
3279        $pages = &$this->getSurveyPages();
3280        $hasconstraints = false;
3281        foreach ($pages as $question_array) {
3282            foreach ($question_array as $question) {
3283                if (count($question["constraints"])) {
3284                    $hasconstraints = true;
3285                }
3286            }
3287        }
3288
3289        if ($hasconstraints) {
3290            $a_xml_writer->xmlStartTag("constraints");
3291            foreach ($pages as $question_array) {
3292                foreach ($question_array as $question) {
3293                    if (count($question["constraints"])) {
3294                        // found constraints
3295                        foreach ($question["constraints"] as $constraint) {
3296                            $attribs = array(
3297                                "sourceref" => $question["question_id"],
3298                                "destref" => $constraint["question"],
3299                                "relation" => $constraint["short"],
3300                                "value" => $constraint["value"],
3301                                "conjunction" => $constraint["conjunction"]
3302                            );
3303                            $a_xml_writer->xmlElement("constraint", $attribs);
3304                        }
3305                    }
3306                }
3307            }
3308            $a_xml_writer->xmlEndTag("constraints");
3309        }
3310
3311        // add the rest of the preferences in qtimetadata tags, because there is no correspondent definition in QTI
3312        $a_xml_writer->xmlStartTag("metadata");
3313
3314        $custom_properties = array();
3315        $custom_properties["evaluation_access"] = $this->getEvaluationAccess();
3316        $custom_properties["status"] = !$this->getOfflineStatus();
3317        $custom_properties["display_question_titles"] = $this->getShowQuestionTitles();
3318        $custom_properties["pool_usage"] = (int) $this->getPoolUsage();
3319
3320        $custom_properties["own_results_view"] = (int) $this->hasViewOwnResults();
3321        $custom_properties["own_results_mail"] = (int) $this->hasMailOwnResults();
3322        $custom_properties["confirmation_mail"] = (int) $this->hasMailConfirmation();
3323
3324        $custom_properties["anon_user_list"] = (int) $this->hasAnonymousUserList();
3325        $custom_properties["mode"] = (int) $this->getMode();
3326        $custom_properties["mode_360_self_eval"] = (int) $this->get360SelfEvaluation();
3327        $custom_properties["mode_360_self_rate"] = (int) $this->get360SelfRaters();
3328        $custom_properties["mode_360_self_appr"] = (int) $this->get360SelfAppraisee();
3329        $custom_properties["mode_360_results"] = $this->get360Results();
3330        $custom_properties["mode_skill_service"] = (int) $this->getSkillService();
3331        $custom_properties["mode_self_eval_results"] = (int) $this->getSelfEvaluationResults();
3332
3333
3334        // :TODO: skills?
3335
3336        // reminder/tutor notification are (currently?) not exportable
3337
3338        foreach ($custom_properties as $label => $value) {
3339            $a_xml_writer->xmlStartTag("metadatafield");
3340            $a_xml_writer->xmlElement("fieldlabel", null, $label);
3341            $a_xml_writer->xmlElement("fieldentry", null, $value);
3342            $a_xml_writer->xmlEndTag("metadatafield");
3343        }
3344
3345        $a_xml_writer->xmlStartTag("metadatafield");
3346        $a_xml_writer->xmlElement("fieldlabel", null, "SCORM");
3347        $md = new ilMD($this->getId(), 0, $this->getType());
3348        $writer = new ilXmlWriter();
3349        $md->toXml($writer);
3350        $metadata = $writer->xmlDumpMem();
3351        $a_xml_writer->xmlElement("fieldentry", null, $metadata);
3352        $a_xml_writer->xmlEndTag("metadatafield");
3353
3354        $a_xml_writer->xmlEndTag("metadata");
3355        $a_xml_writer->xmlEndTag("survey");
3356
3357        $attribs = array("id" => $this->getId());
3358        $a_xml_writer->xmlStartTag("surveyquestions", $attribs);
3359        // add questionblock descriptions
3360        foreach ($pages as $question_array) {
3361            if (count($question_array) > 1) {
3362                $attribs = array("id" => $question_array[0]["question_id"]);
3363                $attribs = array("showQuestiontext" => $question_array[0]["questionblock_show_questiontext"],
3364                    "showBlocktitle" => $question_array[0]["questionblock_show_blocktitle"]);
3365                $a_xml_writer->xmlStartTag("questionblock", $attribs);
3366                if (strlen($question_array[0]["questionblock_title"])) {
3367                    $a_xml_writer->xmlElement("questionblocktitle", null, $question_array[0]["questionblock_title"]);
3368                }
3369            }
3370            foreach ($question_array as $question) {
3371                if (strlen($question["heading"])) {
3372                    $a_xml_writer->xmlElement("textblock", null, $question["heading"]);
3373                }
3374                $questionObject = self::_instanciateQuestion($question["question_id"]);
3375                //questionObject contains all the fields from the database. (loadFromDb)
3376                //we don't need the value from svy_qst_oblig table, we already have the values from svy_question table.
3377                //if ($questionObject !== FALSE) $questionObject->insertXML($a_xml_writer, FALSE, $obligatory_states[$question["question_id"]]);
3378                if ($questionObject !== false) {
3379                    $questionObject->insertXML($a_xml_writer, false);
3380                }
3381            }
3382            if (count($question_array) > 1) {
3383                $a_xml_writer->xmlEndTag("questionblock");
3384            }
3385        }
3386
3387        $a_xml_writer->xmlEndTag("surveyquestions");
3388        $a_xml_writer->xmlEndTag("surveyobject");
3389        $xml = $a_xml_writer->xmlDumpMem(false);
3390        return $xml;
3391    }
3392
3393    /**
3394    * Creates an instance of a question with a given question id
3395    *
3396    * @param integer $question_id The question id
3397    * @return object The question instance
3398    * @access public
3399    */
3400    public static function _instanciateQuestion($question_id)
3401    {
3402        if ($question_id < 1) {
3403            return false;
3404        }
3405        $question_type = SurveyQuestion::_getQuestionType($question_id);
3406        if (strlen($question_type) == 0) {
3407            return false;
3408        }
3409        SurveyQuestion::_includeClass($question_type);
3410        $question = new $question_type();
3411        $question->loadFromDb($question_id);
3412        return $question;
3413    }
3414
3415    /**
3416    * Locates the import directory and the xml file in a directory with an unzipped import file
3417    *
3418    * @return array An associative array containing "dir" (import directory) and "xml" (xml file)
3419    * @access private
3420    */
3421    public function locateImportFiles($a_dir)
3422    {
3423        if (!is_dir($a_dir) || is_int(strpos($a_dir, ".."))) {
3424            return;
3425        }
3426        $importDirectory = "";
3427        $xmlFile = "";
3428
3429        $current_dir = opendir($a_dir);
3430        $files = array();
3431        while ($entryname = readdir($current_dir)) {
3432            $files[] = $entryname;
3433        }
3434
3435        foreach ($files as $file) {
3436            if (is_dir($a_dir . "/" . $file) and ($file != "." and $file != "..")) {
3437                // found directory created by zip
3438                $importDirectory = $a_dir . "/" . $file;
3439            }
3440        }
3441        closedir($current_dir);
3442        if (strlen($importDirectory)) {
3443            // find the xml file
3444            $current_dir = opendir($importDirectory);
3445            $files = array();
3446            while ($entryname = readdir($current_dir)) {
3447                $files[] = $entryname;
3448            }
3449            foreach ($files as $file) {
3450                if (@is_file($importDirectory . "/" . $file) &&
3451                    ($file != "." && $file != "..") &&
3452                    (preg_match("/^[0-9]{10}__[0-9]+__(svy_)*[0-9]+\.[A-Za-z]{1,3}$/", $file) ||
3453                        preg_match("/^[0-9]{10}__[0-9]+__(survey__)*[0-9]+\.[A-Za-z]{1,3}$/", $file))) {
3454                    // found xml file
3455                    $xmlFile = $importDirectory . "/" . $file;
3456                }
3457            }
3458        }
3459        return array("dir" => $importDirectory, "xml" => $xmlFile);
3460    }
3461
3462    /**
3463     * Imports a survey from XML into the ILIAS database
3464     * @param $file_info
3465     * @param $svy_qpl_id
3466     * @return string
3467     * @throws ilFileUtilsException
3468     * @throws ilInvalidSurveyImportFileException
3469     */
3470    public function importObject($file_info, $svy_qpl_id)
3471    {
3472        if ($svy_qpl_id < 1) {
3473            $svy_qpl_id = -1;
3474        }
3475        // check if file was uploaded
3476        $source = $file_info["tmp_name"];
3477        $error = "";
3478        if (($source == 'none') || (!$source) || $file_info["error"] > UPLOAD_ERR_OK) {
3479            $error = $this->lng->txt("import_no_file_selected");
3480        }
3481        // check correct file type
3482        $isXml = false;
3483        $isZip = false;
3484        if ((strcmp($file_info["type"], "text/xml") == 0) || (strcmp($file_info["type"], "application/xml") == 0)) {
3485            $this->log->debug("isXML");
3486            $isXml = true;
3487        }
3488        // too many different mime-types, so we use the suffix
3489        $suffix = pathinfo($file_info["name"]);
3490        if (strcmp(strtolower($suffix["extension"]), "zip") == 0) {
3491            $this->log->debug("isZip");
3492            $isZip = true;
3493        }
3494        if (!$isXml && !$isZip) {
3495            $error = $this->lng->txt("import_wrong_file_type");
3496            $this->log->debug("Survey: Import error. Filetype was \"" . $file_info["type"] . "\"");
3497        }
3498        if (strlen($error) == 0) {
3499            // import file as a survey
3500            $import_dir = $this->getImportDirectory();
3501            $import_subdir = "";
3502            $importfile = "";
3503            if ($isZip) {
3504                $importfile = $import_dir . "/" . $file_info["name"];
3505                ilUtil::moveUploadedFile($source, $file_info["name"], $importfile);
3506                ilUtil::unzip($importfile);
3507                $found = $this->locateImportFiles($import_dir);
3508                if (!((strlen($found["dir"]) > 0) && (strlen($found["xml"]) > 0))) {
3509                    $error = $this->lng->txt("wrong_import_file_structure");
3510                    return $error;
3511                }
3512                $importfile = $found["xml"];
3513                $import_subdir = $found["dir"];
3514            } else {
3515                $importfile = tempnam($import_dir, "survey_import");
3516                ilUtil::moveUploadedFile($source, $file_info["name"], $importfile);
3517            }
3518
3519            $this->log->debug("Import file = $importfile");
3520            $this->log->debug("Import subdir = $import_subdir");
3521
3522            $fh = fopen($importfile, "r");
3523            if (!$fh) {
3524                $error = $this->lng->txt("import_error_opening_file");
3525                return $error;
3526            }
3527            $xml = fread($fh, filesize($importfile));
3528            $result = fclose($fh);
3529            if (!$result) {
3530                $error = $this->lng->txt("import_error_closing_file");
3531                return $error;
3532            }
3533
3534            unset($_SESSION["import_mob_xhtml"]);
3535            if (strpos($xml, "questestinterop")) {
3536                throw new ilInvalidSurveyImportFileException("Unsupported survey version (< 3.8) found.");
3537            } else {
3538                $this->log->debug("survey id = " . $this->getId());
3539                $this->log->debug("question pool id = " . $svy_qpl_id);
3540
3541                $imp = new ilImport();
3542                $config = $imp->getConfig("Modules/Survey");
3543                $config->setQuestionPoolID($svy_qpl_id);
3544                $imp->getMapping()->addMapping("Modules/Survey", "svy", 0, $this->getId());
3545                $imp->importFromDirectory($import_subdir, "svy", "Modules/Survey");
3546                $this->log->debug("config(Modules/survey)->getQuestionPoolId =" . $config->getQuestionPoolID());
3547                return "";
3548
3549                //old code
3550                $import = new SurveyImportParser($svy_qpl_id, "", true);
3551                $import->setSurveyObject($this);
3552                $import->setXMLContent($xml);
3553                $import->startParsing();
3554            }
3555
3556            if (is_array($_SESSION["import_mob_xhtml"])) {
3557                foreach ($_SESSION["import_mob_xhtml"] as $mob) {
3558                    $importfile = $import_subdir . "/" . $mob["uri"];
3559                    if (file_exists($importfile)) {
3560                        if (!$mob["type"]) {
3561                            $mob["type"] = "svy:html";
3562                        }
3563
3564                        $media_object = ilObjMediaObject::_saveTempFileAsMediaObject(basename($importfile), $importfile, false);
3565
3566                        // survey mob
3567                        if ($mob["type"] == "svy:html") {
3568                            ilObjMediaObject::_saveUsage($media_object->getId(), "svy:html", $this->getId());
3569                            $this->setIntroduction(str_replace("src=\"" . $mob["mob"] . "\"", "src=\"" . "il_" . IL_INST_ID . "_mob_" . $media_object->getId() . "\"", $this->getIntroduction()));
3570                            $this->setOutro(str_replace("src=\"" . $mob["mob"] . "\"", "src=\"" . "il_" . IL_INST_ID . "_mob_" . $media_object->getId() . "\"", $this->getOutro()));
3571                        }
3572                        // question mob
3573                        elseif ($import->questions[$mob["id"]]) {
3574                            $new_qid = $import->questions[$mob["id"]];
3575                            ilObjMediaObject::_saveUsage($media_object->getId(), $mob["type"], $new_qid);
3576                            $new_question = SurveyQuestion::_instanciateQuestion($new_qid);
3577                            $qtext = $new_question->getQuestiontext();
3578                            $qtext = ilRTE::_replaceMediaObjectImageSrc($qtext, 0);
3579                            $qtext = str_replace("src=\"" . $mob["mob"] . "\"", "src=\"" . "il_" . IL_INST_ID . "_mob_" . $media_object->getId() . "\"", $qtext);
3580                            $qtext = ilRTE::_replaceMediaObjectImageSrc($qtext, 1);
3581                            $new_question->setQuestiontext($qtext);
3582                            $new_question->saveToDb();
3583
3584                            // also fix existing original in pool
3585                            if ($new_question->getOriginalId()) {
3586                                $pool_question = SurveyQuestion::_instanciateQuestion($new_question->getOriginalId());
3587                                $pool_question->setQuestiontext($qtext);
3588                                $pool_question->saveToDb();
3589                            }
3590                        }
3591                    } else {
3592                        $ilLog = $this->log;
3593                        $ilLog->write("Error: Could not open XHTML mob file for test introduction during test import. File $importfile does not exist!");
3594                    }
3595                }
3596                $this->setIntroduction(ilRTE::_replaceMediaObjectImageSrc($this->getIntroduction(), 1));
3597                $this->setOutro(ilRTE::_replaceMediaObjectImageSrc($this->getOutro(), 1));
3598                $this->saveToDb();
3599            }
3600
3601            // delete import directory
3602            ilUtil::delDir($this->getImportDirectory());
3603        }
3604        return $error;
3605    }
3606
3607    /**
3608     * Clone object
3609     *
3610     * @access public
3611     * @param int ref_id of target container
3612     * @param int copy id
3613     * @return object new svy object
3614     */
3615    public function cloneObject($a_target_id, $a_copy_id = 0, $a_omit_tree = false)
3616    {
3617        $ilDB = $this->db;
3618
3619        $this->loadFromDb();
3620
3621        //survey mode
3622        $svy_type = $this->getMode();
3623
3624        // Copy settings
3625        $newObj = parent::cloneObject($a_target_id, $a_copy_id, $a_omit_tree);
3626        $this->cloneMetaData($newObj);
3627        $newObj->updateMetaData();
3628
3629        $newObj->setAuthor($this->getAuthor());
3630        $newObj->setIntroduction($this->getIntroduction());
3631        $newObj->setOutro($this->getOutro());
3632        $newObj->setEvaluationAccess($this->getEvaluationAccess());
3633        $newObj->setStartDate($this->getStartDate());
3634        $newObj->setEndDate($this->getEndDate());
3635        $newObj->setAnonymize($this->getAnonymize());
3636        $newObj->setShowQuestionTitles($this->getShowQuestionTitles());
3637        $newObj->setTemplate($this->getTemplate());
3638        $newObj->setPoolUsage($this->getPoolUsage());
3639        $newObj->setViewOwnResults($this->hasViewOwnResults());
3640        $newObj->setMailOwnResults($this->hasMailOwnResults());
3641        $newObj->setMailConfirmation($this->hasMailConfirmation());
3642        $newObj->setAnonymousUserList($this->hasAnonymousUserList());
3643
3644        // #12661
3645        if ($this->get360Mode()) {
3646            $newObj->setMode(ilObjSurvey::MODE_360);
3647            $newObj->set360SelfEvaluation($this->get360SelfEvaluation());
3648            $newObj->set360SelfAppraisee($this->get360SelfAppraisee());
3649            $newObj->set360SelfRaters($this->get360SelfRaters());
3650            $newObj->set360Results($this->get360Results());
3651            $newObj->setSkillService($this->getSkillService());
3652        }
3653        //svy mode self eval: skills + view results
3654        if ($svy_type == ilObjSurvey::MODE_SELF_EVAL) {
3655            $newObj->setMode(ilObjSurvey::MODE_SELF_EVAL);
3656            $newObj->setSkillService($this->getSkillService());
3657            $newObj->setSelfEvaluationResults($this->getSelfEvaluationResults());
3658        }
3659
3660        // reminder/notification
3661        $newObj->setReminderStatus($this->getReminderStatus());
3662        $newObj->setReminderStart($this->getReminderStart());
3663        $newObj->setReminderEnd($this->getReminderEnd());
3664        $newObj->setReminderFrequency($this->getReminderFrequency());
3665        $newObj->setReminderTarget($this->getReminderTarget());
3666        $newObj->setReminderTemplate($this->getReminderTemplate());
3667        // reminder_last_sent must not be copied!
3668        $newObj->setTutorNotificationStatus($this->getTutorNotificationStatus());
3669        $newObj->setTutorNotificationRecipients($this->getTutorNotificationRecipients());
3670        $newObj->setTutorNotificationTarget($this->getTutorNotificationTarget());
3671        $newObj->setTutorResultsStatus($this->getTutorResultsStatus());
3672        $newObj->setTutorResultsRecipients($this->getTutorResultsRecipients());
3673
3674        $newObj->setMailNotification($this->getMailNotification());
3675        $newObj->setMailAddresses($this->getMailAddresses());
3676        $newObj->setMailParticipantData($this->getMailParticipantData());
3677
3678        $question_pointer = array();
3679        // clone the questions
3680        $mapping = array();
3681
3682        foreach ($this->questions as $key => $question_id) {
3683            /** @var $question SurveyQuestion */
3684            $question = self::_instanciateQuestion($question_id);
3685            if ($question) { // #10824
3686                $question->id = -1;
3687                $original_id = SurveyQuestion::_getOriginalId($question_id, false);
3688                $question->setObjId($newObj->getId());
3689                $question->saveToDb($original_id);
3690                $newObj->questions[$key] = $question->getId();
3691                $question_pointer[$question_id] = $question->getId();
3692                $mapping[$question_id] = $question->getId();
3693            }
3694        }
3695
3696        //copy online status if object is not the root copy object
3697        $cp_options = ilCopyWizardOptions::_getInstance($a_copy_id);
3698
3699        if (!$cp_options->isRootNode($this->getRefId())) {
3700            $newObj->setOfflineStatus($this->getOfflineStatus());
3701        }
3702
3703        $newObj->saveToDb();
3704        $newObj->cloneTextblocks($mapping);
3705
3706        // #14929
3707        if (($svy_type == ilObjSurvey::MODE_360 || $svy_type == ilObjSurvey::MODE_SELF_EVAL) &&
3708            $this->getSkillService()) {
3709            $src_skills = new ilSurveySkill($this);
3710            $tgt_skills = new ilSurveySkill($newObj);
3711
3712            foreach ($mapping as $src_qst_id => $tgt_qst_id) {
3713                $qst_skill = $src_skills->getSkillForQuestion($src_qst_id);
3714                if ($qst_skill) {
3715                    $tgt_skills->addQuestionSkillAssignment($tgt_qst_id, $qst_skill["base_skill_id"], $qst_skill["tref_id"]);
3716                }
3717            }
3718        }
3719
3720        // clone the questionblocks
3721        $questionblocks = array();
3722        $questionblock_questions = array();
3723        $result = $ilDB->queryF(
3724            "SELECT * FROM svy_qblk_qst WHERE survey_fi = %s",
3725            array('integer'),
3726            array($this->getSurveyId())
3727        );
3728        if ($result->numRows() > 0) {
3729            while ($row = $ilDB->fetchAssoc($result)) {
3730                array_push($questionblock_questions, $row);
3731                $questionblocks[$row["questionblock_fi"]] = $row["questionblock_fi"];
3732            }
3733        }
3734        // create new questionblocks
3735        foreach ($questionblocks as $key => $value) {
3736            $questionblock = self::_getQuestionblock($key);
3737            $questionblock_id = self::_addQuestionblock($questionblock["title"], $questionblock["owner_fi"], $questionblock["show_questiontext"], $questionblock["show_blocktitle"]);
3738            $questionblocks[$key] = $questionblock_id;
3739        }
3740        // create new questionblock questions
3741        foreach ($questionblock_questions as $key => $value) {
3742            if ($questionblocks[$value["questionblock_fi"]] &&
3743                $question_pointer[$value["question_fi"]]) {
3744                $next_id = $ilDB->nextId('svy_qblk_qst');
3745                $affectedRows = $ilDB->manipulateF(
3746                    "INSERT INTO svy_qblk_qst (qblk_qst_id, survey_fi, questionblock_fi, question_fi) " .
3747                    "VALUES (%s, %s, %s, %s)",
3748                    array('integer','integer','integer','integer'),
3749                    array($next_id, $newObj->getSurveyId(), $questionblocks[$value["questionblock_fi"]], $question_pointer[$value["question_fi"]])
3750                );
3751            }
3752        }
3753
3754        // clone the constraints
3755        $constraints = self::_getConstraints($this->getSurveyId());
3756        $newConstraints = array();
3757        foreach ($constraints as $key => $constraint) {
3758            if ($question_pointer[$constraint["for_question"]] &&
3759                $question_pointer[$constraint["question"]]) {
3760                if (!array_key_exists($constraint['id'], $newConstraints)) {
3761                    $constraint_id = $newObj->addConstraint($question_pointer[$constraint["question"]], $constraint["relation_id"], $constraint["value"], $constraint['conjunction']);
3762                    $newConstraints[$constraint['id']] = $constraint_id;
3763                }
3764                $newObj->addConstraintToQuestion($question_pointer[$constraint["for_question"]], $newConstraints[$constraint['id']]);
3765            }
3766        }
3767
3768        // #16210 - clone LP settings
3769        $obj_settings = new ilLPObjSettings($this->getId());
3770        $obj_settings->cloneSettings($newObj->getId());
3771        unset($obj_settings);
3772
3773        return $newObj;
3774    }
3775
3776    public function getTextblock($question_id)
3777    {
3778        $ilDB = $this->db;
3779        $result = $ilDB->queryF(
3780            "SELECT * FROM svy_svy_qst WHERE question_fi = %s",
3781            array('integer'),
3782            array($question_id)
3783        );
3784        if ($result->numRows()) {
3785            $row = $ilDB->fetchAssoc($result);
3786            return $row["heading"];
3787        } else {
3788            return "";
3789        }
3790    }
3791
3792    /**
3793    * Clones the textblocks of survey questions
3794    *
3795    * @access public
3796    */
3797    public function cloneTextblocks($mapping)
3798    {
3799        foreach ($mapping as $original_id => $new_id) {
3800            $textblock = $this->getTextblock($original_id);
3801            $this->saveHeading(ilUtil::stripSlashes($textblock, true, ilObjAdvancedEditing::_getUsedHTMLTagsAsString("survey")), $new_id);
3802        }
3803    }
3804
3805    /**
3806    * creates data directory for export files
3807    * (data_dir/svy_data/svy_<id>/export, depending on data
3808    * directory that is set in ILIAS setup/ini)
3809    *
3810    * @throws ilSurveyException
3811    */
3812    public function createExportDirectory()
3813    {
3814        $svy_data_dir = ilUtil::getDataDir() . "/svy_data";
3815        ilUtil::makeDir($svy_data_dir);
3816        if (!is_writable($svy_data_dir)) {
3817            throw new ilSurveyException("Survey Data Directory (" . $svy_data_dir . ") not writeable.");
3818        }
3819
3820        // create learning module directory (data_dir/lm_data/lm_<id>)
3821        $svy_dir = $svy_data_dir . "/svy_" . $this->getId();
3822        ilUtil::makeDir($svy_dir);
3823        if (!@is_dir($svy_dir)) {
3824            throw new ilSurveyException("Creation of Survey Directory failed.");
3825        }
3826        // create Export subdirectory (data_dir/lm_data/lm_<id>/Export)
3827        $export_dir = $svy_dir . "/export";
3828        ilUtil::makeDir($export_dir);
3829        if (!@is_dir($export_dir)) {
3830            throw new ilSurveyException("Creation of Export Directory failed.");
3831        }
3832    }
3833
3834    /**
3835    * get export directory of survey
3836    */
3837    public function getExportDirectory()
3838    {
3839        $export_dir = ilUtil::getDataDir() . "/svy_data" . "/svy_" . $this->getId() . "/export";
3840
3841        return $export_dir;
3842    }
3843
3844    /**
3845    * creates data directory for import files
3846    * (data_dir/svy_data/svy_<id>/import, depending on data
3847    * directory that is set in ILIAS setup/ini)
3848    *
3849    * @throws ilSurveyException
3850    */
3851    public function createImportDirectory()
3852    {
3853        $svy_data_dir = ilUtil::getDataDir() . "/svy_data";
3854        ilUtil::makeDir($svy_data_dir);
3855
3856        if (!is_writable($svy_data_dir)) {
3857            throw new ilSurveyException("Survey Data Directory (" . $svy_data_dir . ") not writeable.");
3858        }
3859
3860        // create test directory (data_dir/svy_data/svy_<id>)
3861        $svy_dir = $svy_data_dir . "/svy_" . $this->getId();
3862        ilUtil::makeDir($svy_dir);
3863        if (!@is_dir($svy_dir)) {
3864            throw new ilSurveyException("Creation of Survey Directory failed.");
3865        }
3866
3867        // create import subdirectory (data_dir/svy_data/svy_<id>/import)
3868        $import_dir = $svy_dir . "/import";
3869        ilUtil::makeDir($import_dir);
3870        if (!@is_dir($import_dir)) {
3871            throw new ilSurveyException("Creation of Import Directory failed.");
3872        }
3873    }
3874
3875    /**
3876    * get import directory of survey
3877    */
3878    public function getImportDirectory()
3879    {
3880        $import_dir = ilUtil::getDataDir() . "/svy_data" .
3881            "/svy_" . $this->getId() . "/import";
3882        if (!is_dir($import_dir)) {
3883            ilUtil::makeDirParents($import_dir);
3884        }
3885        if (@is_dir($import_dir)) {
3886            return $import_dir;
3887        } else {
3888            return false;
3889        }
3890    }
3891
3892    public function saveHeading($heading = "", $insertbefore)
3893    {
3894        $ilDB = $this->db;
3895        if ($heading) {
3896            $affectedRows = $ilDB->manipulateF(
3897                "UPDATE svy_svy_qst SET heading=%s WHERE survey_fi=%s AND question_fi=%s",
3898                array('text','integer','integer'),
3899                array($heading, $this->getSurveyId(), $insertbefore)
3900            );
3901        } else {
3902            $affectedRows = $ilDB->manipulateF(
3903                "UPDATE svy_svy_qst SET heading=%s WHERE survey_fi=%s AND question_fi=%s",
3904                array('text','integer','integer'),
3905                array(null, $this->getSurveyId(), $insertbefore)
3906            );
3907        }
3908    }
3909
3910    public function isAnonymousKey($key)
3911    {
3912        $ilDB = $this->db;
3913
3914        $result = $ilDB->queryF(
3915            "SELECT anonymous_id FROM svy_anonymous WHERE survey_key = %s AND survey_fi = %s",
3916            array('text','integer'),
3917            array($key, $this->getSurveyId())
3918        );
3919        return ($result->numRows() == 1) ? true : false;
3920    }
3921
3922    public function bindSurveyCodeToUser($user_id, $code)
3923    {
3924        $ilDB = $this->db;
3925
3926        if ($user_id == ANONYMOUS_USER_ID) {
3927            return;
3928        }
3929
3930        if ($this->checkSurveyCode($code)) {
3931            $ilDB->manipulate("UPDATE svy_anonymous" .
3932                " SET user_key = " . $ilDB->quote(md5($user_id), "text") .
3933                " WHERE survey_key = " . $ilDB->quote($code, "text"));
3934        }
3935    }
3936
3937    public function isAnonymizedParticipant($key)
3938    {
3939        $ilDB = $this->db;
3940
3941        $result = $ilDB->queryF(
3942            "SELECT finished_id FROM svy_finished WHERE anonymous_id = %s AND survey_fi = %s",
3943            array('text','integer'),
3944            array($key, $this->getSurveyId())
3945        );
3946        return ($result->numRows() == 1) ? true : false;
3947    }
3948
3949    public function checkSurveyCode($code)
3950    {
3951        if ($this->isAnonymousKey($code)) {
3952            if ($this->isSurveyStarted("", $code) == 1) {
3953                return false;
3954            } else {
3955                return true;
3956            }
3957        } else {
3958            return false;
3959        }
3960    }
3961
3962    /**
3963    * Returns a list of survey codes for file export
3964    *
3965    * @param array $a_array An array of all survey codes that should be exported
3966    * @return string A comma separated list of survey codes an URLs for file export
3967    * @access public
3968    */
3969    public function getSurveyCodesForExport(array $a_codes = null, array $a_ids = null)
3970    {
3971        $ilDB = $this->db;
3972        $ilUser = $this->user;
3973        $lng = $this->lng;
3974
3975        $sql = "SELECT svy_anonymous.*, svy_finished.state" .
3976            " FROM svy_anonymous" .
3977            " LEFT JOIN svy_finished ON (svy_anonymous.survey_key = svy_finished.anonymous_id)" .
3978            " WHERE svy_anonymous.survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
3979            " AND svy_anonymous.user_key IS NULL";
3980
3981        if ($a_codes) {
3982            $sql .= " AND " . $ilDB->in("svy_anonymous.survey_key", $a_codes, "", "text");
3983        } elseif ($a_ids) {
3984            $sql .= " AND " . $ilDB->in("svy_anonymous.anonymous_id", $a_ids, "", "text");
3985        }
3986
3987        $export = array();
3988
3989        // #14905
3990        $titles = array();
3991        $titles[] = '"' . $lng->txt("survey_code") . '"';
3992        $titles[] = '"' . $lng->txt("email") . '"';
3993        $titles[] = '"' . $lng->txt("lastname") . '"';
3994        $titles[] = '"' . $lng->txt("firstname") . '"';
3995        $titles[] = '"' . $lng->txt("create_date") . '"';
3996        $titles[] = '"' . $lng->txt("used") . '"';
3997        $titles[] = '"' . $lng->txt("mail_sent_short") . '"';
3998        $titles[] = '"' . $lng->txt("survey_code_url") . '"';
3999        $export[] = implode(";", $titles);
4000
4001        $result = $ilDB->query($sql);
4002        $default_lang = $ilUser->getPref("survey_code_language");
4003        while ($row = $ilDB->fetchAssoc($result)) {
4004            $item = array();
4005            $item[] = $row["survey_key"];
4006
4007            if ($row["externaldata"]) {
4008                $ext = unserialize($row["externaldata"]);
4009                $item[] = $ext["email"];
4010                $item[] = $ext["lastname"];
4011                $item[] = $ext["firstname"];
4012            } else {
4013                $item[] = "";
4014                $item[] = "";
4015                $item[] = "";
4016            }
4017
4018            // No relative (today, tomorrow...) dates in export.
4019            $date = new ilDateTime($row['tstamp'], IL_CAL_UNIX);
4020            $item[] = $date->get(IL_CAL_DATETIME);
4021
4022            $item[] = ($this->isSurveyCodeUsed($row["survey_key"])) ? 1 : 0;
4023            $item[] = ($row["sent"]) ? 1 : 0;
4024
4025            $params = array("accesscode" => $row["survey_key"]);
4026            if ($default_lang) {
4027                $params["lang"] = $default_lang;
4028            }
4029            $item[] = ilLink::_getLink($this->getRefId(), "svy", $params);
4030
4031            $export[] = '"' . implode('";"', $item) . '"';
4032        }
4033        return implode("\n", $export);
4034    }
4035
4036    /**
4037    * Fetches the data for the survey codes table
4038    *
4039    * @param string $lang Language for the survey code URL
4040    * @return array The requested data
4041    * @access public
4042    */
4043    public function getSurveyCodesTableData(array $ids = null, $lang = null)
4044    {
4045        $ilDB = $this->db;
4046
4047        $codes = array();
4048
4049        $sql = "SELECT svy_anonymous.*, svy_finished.state" .
4050            " FROM svy_anonymous" .
4051            " LEFT JOIN svy_finished ON (svy_anonymous.survey_key = svy_finished.anonymous_id)" .
4052            " WHERE svy_anonymous.survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") /*.
4053            " AND svy_anonymous.user_key IS NULL" */; // #15860
4054
4055        if ($ids) {
4056            $sql .= " AND " . $ilDB->in("svy_anonymous.anonymous_id", $ids, "", "integer");
4057        }
4058
4059        $sql .= " ORDER BY tstamp, survey_key ASC";
4060        $result = $ilDB->query($sql);
4061        if ($result->numRows() > 0) {
4062            while ($row = $ilDB->fetchAssoc($result)) {
4063                $href = "";
4064                $used = false;
4065                if ($this->isSurveyCodeUsed($row["survey_key"])) {
4066                    $used = true;
4067                } else {
4068                    $params = array("accesscode" => $row["survey_key"]);
4069                    if ($lang) {
4070                        $params["lang"] = $lang;
4071                    }
4072                    $href = ilLink::_getLink($this->getRefId(), "svy", $params);
4073                }
4074
4075
4076                $item = array(
4077                    'id' => $row["anonymous_id"],
4078                    'code' => $row["survey_key"],
4079                    'date' => $row["tstamp"],
4080                    'used' => $used,
4081                    'sent' => $row['sent'],
4082                    'href' => $href,
4083                    'email' => '',
4084                    'last_name' => '',
4085                    'first_name' => ''
4086                );
4087
4088                if ($row["externaldata"]) {
4089                    $ext = unserialize($row["externaldata"]);
4090                    $item['email'] = $ext['email'];
4091                    $item['last_name'] = $ext['lastname'];
4092                    $item['first_name'] = $ext['firstname'];
4093                }
4094
4095                array_push($codes, $item);
4096            }
4097        }
4098        return $codes;
4099    }
4100
4101    public function isSurveyCodeUsed($code)
4102    {
4103        $ilDB = $this->db;
4104        $result = $ilDB->queryF(
4105            "SELECT finished_id FROM svy_finished WHERE survey_fi = %s AND anonymous_id = %s",
4106            array('integer','text'),
4107            array($this->getSurveyId(), $code)
4108        );
4109        return ($result->numRows() > 0) ? true : false;
4110    }
4111
4112    public function isSurveyCodeUnique($code)
4113    {
4114        $ilDB = $this->db;
4115        $result = $ilDB->queryF(
4116            "SELECT anonymous_id FROM svy_anonymous WHERE survey_fi = %s AND survey_key = %s",
4117            array('integer','text'),
4118            array($this->getSurveyId(), $code)
4119        );
4120        return ($result->numRows() > 0) ? false : true;
4121    }
4122
4123    public function createSurveyCodes($nrOfCodes)
4124    {
4125        $ilDB = $this->db;
4126
4127        $res = array();
4128
4129        for ($i = 0; $i < $nrOfCodes; $i++) {
4130            $next_id = $ilDB->nextId('svy_anonymous');
4131            $ilDB->manipulateF(
4132                "INSERT INTO svy_anonymous (anonymous_id, survey_key, survey_fi, tstamp) " .
4133                "VALUES (%s, %s, %s, %s)",
4134                array('integer','text','integer','integer'),
4135                array($next_id, $this->createNewAccessCode(), $this->getSurveyId(), time())
4136            );
4137            $res[] = $next_id;
4138        }
4139
4140        return $res;
4141    }
4142
4143    public function importSurveyCode($a_anonymize_key, $a_created, $a_data)
4144    {
4145        $ilDB = $this->db;
4146
4147        $next_id = $ilDB->nextId('svy_anonymous');
4148        $ilDB->manipulateF(
4149            "INSERT INTO svy_anonymous (anonymous_id, survey_key, survey_fi, externaldata, tstamp) " .
4150            "VALUES (%s, %s, %s, %s, %s)",
4151            array('integer','text','integer','text','integer'),
4152            array($next_id, $a_anonymize_key, $this->getSurveyId(), serialize($a_data), $a_created)
4153        );
4154    }
4155
4156    public function createSurveyCodesForExternalData($data)
4157    {
4158        $ilDB = $this->db;
4159
4160        $ids = array();
4161        foreach ($data as $dataset) {
4162            $anonymize_key = $this->createNewAccessCode();
4163            $next_id = $ilDB->nextId('svy_anonymous');
4164            $affectedRows = $ilDB->manipulateF(
4165                "INSERT INTO svy_anonymous (anonymous_id, survey_key, survey_fi, externaldata, tstamp) " .
4166                "VALUES (%s, %s, %s, %s, %s)",
4167                array('integer','text','integer','text','integer'),
4168                array($next_id, $anonymize_key, $this->getSurveyId(), serialize($dataset), time())
4169            );
4170            $ids[] = $next_id;
4171        }
4172        return $ids;
4173    }
4174
4175    public function sendCodes($not_sent, $subject, $message, $lang)
4176    {
4177        global $DIC;
4178        /*
4179         * 0 = all
4180         * 1 = not sent
4181         * 2 = finished
4182         * 3 = not finished
4183         */
4184        $check_finished = ($not_sent > 1);
4185
4186
4187        $mail = new ilMail(ANONYMOUS_USER_ID);
4188        $recipients = $this->getExternalCodeRecipients($check_finished);
4189        foreach ($recipients as $data) {
4190            if ($data['email'] && $data['code']) {
4191                $do_send = false;
4192                switch ((int) $not_sent) {
4193                    case 1:
4194                        $do_send = !(bool) $data['sent'];
4195                        break;
4196
4197                    case 2:
4198                        $do_send = $data['finished'];
4199                        break;
4200
4201                    case 3:
4202                        $do_send = !$data['finished'];
4203                        break;
4204
4205                    default:
4206                        $do_send = true;
4207                        break;
4208                }
4209                if ($do_send) {
4210                    // build text
4211                    $messagetext = $message;
4212                    $url = ilLink::_getLink(
4213                        $this->getRefId(),
4214                        "svy",
4215                        array(
4216                            "accesscode" => $data["code"],
4217                            "lang" => $lang
4218                        )
4219                    );
4220                    $messagetext = str_replace('[url]', $url, $messagetext);
4221                    foreach ($data as $key => $value) {
4222                        $messagetext = str_replace('[' . $key . ']', $value, $messagetext);
4223                    }
4224
4225                    // send mail
4226                    $mail->enqueue(
4227                        $data['email'], // to
4228                        "", // cc
4229                        "", // bcc
4230                        $subject, // subject
4231                        $messagetext, // message
4232                        array() // attachments
4233                    );
4234                }
4235            }
4236        }
4237
4238        $ilDB = $this->db;
4239        $ilDB->manipulateF(
4240            "UPDATE svy_anonymous SET sent = %s WHERE survey_fi = %s AND externaldata IS NOT NULL",
4241            array('integer','integer'),
4242            array(1, $this->getSurveyId())
4243        );
4244    }
4245
4246    public function getExternalCodeRecipients($a_check_finished = false)
4247    {
4248        $ilDB = $this->db;
4249        $result = $ilDB->queryF(
4250            "SELECT survey_key code, externaldata, sent FROM svy_anonymous WHERE survey_fi = %s",
4251            array('integer'),
4252            array($this->getSurveyId())
4253        );
4254        $res = array();
4255        while ($row = $ilDB->fetchAssoc($result)) {
4256            if (!$row['externaldata']) {
4257                continue;
4258            }
4259
4260            $externaldata = unserialize($row['externaldata']);
4261            if (!$externaldata['email']) {
4262                continue;
4263            }
4264
4265            $externaldata['code'] = $row['code'];
4266            $externaldata['sent'] = $row['sent'];
4267
4268            if ($a_check_finished) {
4269                #23294
4270                //$externaldata['finished'] =  $this->isSurveyCodeUsed($row['code']);
4271                $externaldata['finished'] = $this->isSurveyFinishedByCode($row['code']);
4272            }
4273
4274            array_push($res, $externaldata);
4275        }
4276        return $res;
4277    }
4278
4279    /**
4280     * Get if survey is finished for an specific anonymous user code.
4281     * @param $a_code anonymous user code
4282     * @return bool
4283     */
4284    public function isSurveyFinishedByCode($a_code)
4285    {
4286        $result = $this->db->queryF(
4287            "SELECT state FROM svy_finished WHERE survey_fi = %s AND anonymous_id = %s",
4288            array('integer','text'),
4289            array($this->getSurveyId(), $a_code)
4290        );
4291
4292        $row = $this->db->fetchAssoc($result);
4293
4294        return $row['state'];
4295    }
4296
4297    /**
4298    * Deletes a given survey access code
4299    *
4300    * @param string $survey_code	The survey code that should be deleted
4301    */
4302    public function deleteSurveyCode($survey_code)
4303    {
4304        $ilDB = $this->db;
4305
4306        if (strlen($survey_code) > 0) {
4307            $affectedRows = $ilDB->manipulateF(
4308                "DELETE FROM svy_anonymous WHERE survey_fi = %s AND survey_key = %s",
4309                array('integer', 'text'),
4310                array($this->getSurveyId(), $survey_code)
4311            );
4312        }
4313    }
4314
4315    /**
4316    * Returns a survey access code that was saved for a registered user
4317    *
4318    * @param int $user_id	The database id of the user
4319    * @return string The survey access code of the user
4320    */
4321    public function getUserAccessCode($user_id)
4322    {
4323        $ilDB = $this->db;
4324        $access_code = "";
4325        $result = $ilDB->queryF(
4326            "SELECT survey_key FROM svy_anonymous WHERE survey_fi = %s AND user_key = %s",
4327            array('integer','text'),
4328            array($this->getSurveyId(), md5($user_id))
4329        );
4330        if ($result->numRows()) {
4331            $row = $ilDB->fetchAssoc($result);
4332            $access_code = $row["survey_key"];
4333        }
4334        return $access_code;
4335    }
4336
4337    /**
4338    * Saves a survey access code for a registered user to the database
4339    *
4340    * @param int $user_id	The database id of the user
4341    * @param string $access_code The survey access code
4342    */
4343    public function saveUserAccessCode($user_id, $access_code)
4344    {
4345        $ilDB = $this->db;
4346
4347        // not really sure what to do about ANONYMOUS_USER_ID
4348
4349        $next_id = $ilDB->nextId('svy_anonymous');
4350        $affectedRows = $ilDB->manipulateF(
4351            "INSERT INTO svy_anonymous (anonymous_id, survey_key, survey_fi, user_key, tstamp) " .
4352            "VALUES (%s, %s, %s, %s, %s)",
4353            array('integer','text', 'integer', 'text', 'integer'),
4354            array($next_id, $access_code, $this->getSurveyId(), md5($user_id), time())
4355        );
4356    }
4357
4358    /**
4359    * Returns a new, unused survey access code
4360    *
4361    * @return	string A new survey access code
4362    */
4363    public function createNewAccessCode()
4364    {
4365        // create a 5 character code
4366        $codestring = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
4367        mt_srand();
4368        $code = "";
4369        for ($i = 1; $i <= 5; $i++) {
4370            $index = mt_rand(0, strlen($codestring) - 1);
4371            $code .= substr($codestring, $index, 1);
4372        }
4373        // verify it against the database
4374        while (!$this->isSurveyCodeUnique($code)) {
4375            $code = $this->createNewAccessCode();
4376        }
4377        return $code;
4378    }
4379
4380    public function getLastAccess($finished_id)
4381    {
4382        $ilDB = $this->db;
4383
4384        $result = $ilDB->queryF(
4385            "SELECT tstamp FROM svy_answer WHERE active_fi = %s ORDER BY tstamp DESC",
4386            array('integer'),
4387            array($finished_id)
4388        );
4389        if ($result->numRows()) {
4390            $row = $ilDB->fetchAssoc($result);
4391            return $row["tstamp"];
4392        } else {
4393            $result = $ilDB->queryF(
4394                "SELECT tstamp FROM svy_finished WHERE finished_id = %s",
4395                array('integer'),
4396                array($finished_id)
4397            );
4398            if ($result->numRows()) {
4399                $row = $ilDB->fetchAssoc($result);
4400                return $row["tstamp"];
4401            }
4402        }
4403        return "";
4404    }
4405
4406    /**
4407    * Prepares a string for a text area output in surveys
4408    *
4409    * @param string $txt_output String which should be prepared for output
4410    * @access public
4411    */
4412    public function prepareTextareaOutput($txt_output)
4413    {
4414        return ilUtil::prepareTextareaOutput($txt_output, $prepare_for_latex_output);
4415    }
4416
4417    /**
4418    * Checks if a given string contains HTML or not
4419    *
4420    * @param string $a_text Text which should be checked
4421    * @return boolean
4422    * @access public
4423    */
4424    public function isHTML($a_text)
4425    {
4426        if (preg_match("/<[^>]*?>/", $a_text)) {
4427            return true;
4428        } else {
4429            return false;
4430        }
4431    }
4432
4433    /**
4434    * Creates an XML material tag from a plain text or xhtml text
4435    *
4436    * @param object $a_xml_writer Reference to the ILIAS XML writer
4437    * @param string $a_material plain text or html text containing the material
4438    * @return string XML material tag
4439    * @access public
4440    */
4441    public function addMaterialTag(&$a_xml_writer, $a_material, $close_material_tag = true, $add_mobs = true, $attribs = null)
4442    {
4443        $a_xml_writer->xmlStartTag("material", $attribs);
4444        $attrs = array(
4445            "type" => "text/plain"
4446        );
4447        if ($this->isHTML($a_material)) {
4448            $attrs["type"] = "text/xhtml";
4449        }
4450        $mattext = ilRTE::_replaceMediaObjectImageSrc($a_material, 0);
4451        $a_xml_writer->xmlElement("mattext", $attrs, $mattext);
4452
4453        if ($add_mobs) {
4454            $mobs = ilObjMediaObject::_getMobsOfObject("svy:html", $this->getId());
4455            foreach ($mobs as $mob) {
4456                $mob_id = "il_" . IL_INST_ID . "_mob_" . $mob;
4457                if (strpos($mattext, $mob_id) !== false) {
4458                    $mob_obj = new ilObjMediaObject($mob);
4459                    $imgattrs = array(
4460                        "label" => $mob_id,
4461                        "uri" => "objects/" . "il_" . IL_INST_ID . "_mob_" . $mob . "/" . $mob_obj->getTitle(),
4462                        "type" => "svy:html",
4463                        "id" => $this->getId()
4464                    );
4465                    $a_xml_writer->xmlElement("matimage", $imgattrs, null);
4466                }
4467            }
4468        }
4469        if ($close_material_tag) {
4470            $a_xml_writer->xmlEndTag("material");
4471        }
4472    }
4473
4474    /**
4475     * Checks if the survey code can be exported with the survey evaluation. In some cases this may be
4476     * necessary but usually you should prevent it because people who sent the survey codes could connect
4477     * real people with the survey code in the evaluation and undermine the anonymity
4478     *
4479     * @return boolean TRUE if the survey is anonymized and the survey code may be shown in the export file
4480     * @author Helmut Schottmüller
4481     **/
4482    public function canExportSurveyCode()
4483    {
4484        if ($this->getAnonymize() != self::ANONYMIZE_OFF) {
4485            if ($this->surveyCodeSecurity == false) {
4486                return true;
4487            }
4488        }
4489        return false;
4490    }
4491
4492    /**
4493    * Convert a print output to XSL-FO
4494    *
4495    * @param string $print_output The print output
4496    * @return string XSL-FO code
4497    * @access public
4498    */
4499    public function processPrintoutput2FO($print_output)
4500    {
4501        if (extension_loaded("tidy")) {
4502            $config = array(
4503                "indent" => false,
4504                "output-xml" => true,
4505                "numeric-entities" => true
4506            );
4507            $tidy = new tidy();
4508            $tidy->parseString($print_output, $config, 'utf8');
4509            $tidy->cleanRepair();
4510            $print_output = tidy_get_output($tidy);
4511            $print_output = preg_replace("/^.*?(<html)/", "\\1", $print_output);
4512        } else {
4513            $print_output = str_replace("&nbsp;", "&#160;", $print_output);
4514            $print_output = str_replace("&otimes;", "X", $print_output);
4515
4516            // #17680 - metric questions use &#160; in print view
4517            $print_output = str_replace("&gt;", "~|gt|~", $print_output);		// see #21550
4518            $print_output = str_replace("&lt;", "~|lt|~", $print_output);
4519            $print_output = str_replace("&#160;", "~|nbsp|~", $print_output);
4520            $print_output = preg_replace('/&(?!amp)/', '&amp;', $print_output);
4521            $print_output = str_replace("~|nbsp|~", "&#160;", $print_output);
4522            $print_output = str_replace("~|gt|~", "&gt;", $print_output);
4523            $print_output = str_replace("~|lt|~", "&lt;", $print_output);
4524        }
4525        $xsl = file_get_contents("./Modules/Survey/xml/question2fo.xsl");
4526
4527        // additional font support
4528        $xsl = str_replace(
4529            'font-family="Helvetica, unifont"',
4530            'font-family="' . $GLOBALS['ilSetting']->get('rpc_pdf_font', 'Helvetica, unifont') . '"',
4531            $xsl
4532        );
4533        $args = array( '/_xml' => $print_output, '/_xsl' => $xsl );
4534        $xh = xslt_create();
4535        $params = array();
4536        try {
4537            $output = xslt_process($xh, "arg:/_xml", "arg:/_xsl", null, $args, $params);
4538        } catch (Exception $e) {
4539            $this->log->error("Print XSLT failed:");
4540            $this->log->error("Content: " . $print_output);
4541            $this->log->error("Xsl: " . $xsl);
4542            throw ($e);
4543        }
4544        xslt_error($xh);
4545        xslt_free($xh);
4546
4547        return $output;
4548    }
4549
4550    /**
4551    * Delivers a PDF file from a XSL-FO string
4552    *
4553    * @param string $fo The XSL-FO string
4554    * @access public
4555    */
4556    public function deliverPDFfromFO($fo)
4557    {
4558        $ilLog = $this->log;
4559
4560        $fo_file = ilUtil::ilTempnam() . ".fo";
4561        $fp = fopen($fo_file, "w");
4562        fwrite($fp, $fo);
4563        fclose($fp);
4564
4565        try {
4566            $pdf_base64 = ilRpcClientFactory::factory('RPCTransformationHandler')->ilFO2PDF($fo);
4567            ilUtil::deliverData($pdf_base64->scalar, ilUtil::getASCIIFilename($this->getTitle()) . ".pdf", "application/pdf");
4568            return true;
4569        } catch (Exception $e) {
4570            $ilLog->write(__METHOD__ . ': ' . $e->getMessage());
4571            return false;
4572        }
4573    }
4574
4575    /**
4576    * Checks whether or not a question plugin with a given name is active
4577    *
4578    * @param string $a_pname The plugin name
4579    * @access public
4580    */
4581    public function isPluginActive($a_pname)
4582    {
4583        $ilPluginAdmin = $this->plugin_admin;
4584        if ($ilPluginAdmin->isActive(IL_COMP_MODULE, "SurveyQuestionPool", "svyq", $a_pname)) {
4585            return true;
4586        } else {
4587            return false;
4588        }
4589    }
4590
4591    /**
4592    * Sets the survey id
4593    *
4594    * @param integer $survey_id The survey id
4595    */
4596    public function setSurveyId($survey_id)
4597    {
4598        $this->survey_id = $survey_id;
4599    }
4600
4601    /**
4602    * Returns a data of all users specified by id list
4603    *
4604    * @param $ids array of user id's
4605    * @return array The user data "usr_id, login, lastname, firstname, clientip" of the users with id as key
4606    */
4607    public function &getUserData($ids)
4608    {
4609        $ilDB = $this->db;
4610
4611        if (!is_array($ids) || count($ids) == 0) {
4612            return array();
4613        }
4614
4615        $result = $ilDB->query("SELECT usr_id, login, lastname, firstname FROM usr_data WHERE " . $ilDB->in('usr_id', $ids, false, 'integer') . " ORDER BY login");
4616        $result_array = array();
4617        while ($row = $ilDB->fetchAssoc($result)) {
4618            $result_array[$row["usr_id"]] = $row;
4619        }
4620        return $result_array;
4621    }
4622
4623    public function getMailNotification()
4624    {
4625        return $this->mailnotification;
4626    }
4627
4628    public function setMailNotification($a_notification)
4629    {
4630        $this->mailnotification = ($a_notification) ? true : false;
4631    }
4632
4633    public function getMailAddresses()
4634    {
4635        return $this->mailaddresses;
4636    }
4637
4638    public function setMailAddresses($a_addresses)
4639    {
4640        $this->mailaddresses = $a_addresses;
4641    }
4642
4643    public function getMailParticipantData()
4644    {
4645        return $this->mailparticipantdata;
4646    }
4647
4648    public function setMailParticipantData($a_data)
4649    {
4650        $this->mailparticipantdata = $a_data;
4651    }
4652
4653    public function setStartTime($finished_id, $first_question)
4654    {
4655        $ilDB = $this->db;
4656        $time = time();
4657        //primary for table svy_times
4658        $id = $ilDB->nextId('svy_times');
4659        $_SESSION['svy_entered_page'] = $time;
4660        $affectedRows = $ilDB->manipulateF(
4661            "INSERT INTO svy_times (id, finished_fi, entered_page, left_page, first_question) VALUES (%s, %s, %s, %s,%s)",
4662            array('integer','integer', 'integer', 'integer', 'integer'),
4663            array($id, $finished_id, $time, null, $first_question)
4664        );
4665    }
4666
4667    public function setEndTime($finished_id)
4668    {
4669        $ilDB = $this->db;
4670        $time = time();
4671        $affectedRows = $ilDB->manipulateF(
4672            "UPDATE svy_times SET left_page = %s WHERE finished_fi = %s AND entered_page = %s",
4673            array('integer', 'integer', 'integer'),
4674            array($time, $finished_id, $_SESSION['svy_entered_page'])
4675        );
4676        unset($_SESSION['svy_entered_page']);
4677    }
4678
4679    public function getWorkingtimeForParticipant($finished_id)
4680    {
4681        $ilDB = $this->db;
4682
4683        $result = $ilDB->queryF(
4684            "SELECT * FROM svy_times WHERE finished_fi = %s",
4685            array('integer'),
4686            array($finished_id)
4687        );
4688        $total = 0;
4689        while ($row = $ilDB->fetchAssoc($result)) {
4690            if ($row['left_page'] > 0 && $row['entered_page'] > 0) {
4691                $total += $row['left_page'] - $row['entered_page'];
4692            }
4693        }
4694        return $total;
4695    }
4696
4697    public function setTemplate($template_id)
4698    {
4699        $this->template_id = (int) $template_id;
4700    }
4701
4702    public function getTemplate()
4703    {
4704        return $this->template_id;
4705    }
4706
4707    public function updateOrder(array $a_order)
4708    {
4709        if (sizeof($this->questions) == sizeof($a_order)) {
4710            $this->questions = array_flip($a_order);
4711            $this->saveQuestionsToDB();
4712        }
4713    }
4714
4715    public function getPoolUsage()
4716    {
4717        return $this->pool_usage;
4718    }
4719
4720    public function setPoolUsage($a_value)
4721    {
4722        $this->pool_usage = (bool) $a_value;
4723    }
4724
4725    /**
4726     * Get current pool status
4727     *
4728     * @return bool
4729     */
4730    public function isPoolActive()
4731    {
4732        $use_pool = (bool) $this->getPoolUsage();
4733        $template_settings = $this->getTemplate();
4734        if ($template_settings) {
4735            $template_settings = new ilSettingsTemplate($template_settings);
4736            $template_settings = $template_settings->getSettings();
4737            $template_settings = $template_settings["use_pool"];
4738            if ($template_settings && $template_settings["hide"]) {
4739                $use_pool = (bool) $template_settings["value"];
4740            }
4741        }
4742        return $use_pool;
4743    }
4744
4745    /**
4746     * Apply settings template
4747     *
4748     * @param int $template_id
4749     */
4750    public function applySettingsTemplate($template_id)
4751    {
4752        if (!$template_id) {
4753            return;
4754        }
4755
4756        $template = new ilSettingsTemplate($template_id);
4757        $template_settings = $template->getSettings();
4758        //ilUtil::dumpVar($template_settings); exit;
4759        if ($template_settings) {
4760            if ($template_settings["show_question_titles"] !== null) {
4761                if ($template_settings["show_question_titles"]["value"]) {
4762                    $this->setShowQuestionTitles(true);
4763                } else {
4764                    $this->setShowQuestionTitles(false);
4765                }
4766            }
4767
4768            if ($template_settings["use_pool"] !== null) {
4769                if ($template_settings["use_pool"]["value"]) {
4770                    $this->setPoolUsage(true);
4771                } else {
4772                    $this->setPoolUsage(false);
4773                }
4774            }
4775
4776
4777            /* see #0021719
4778            if($template_settings["anonymization_options"]["value"])
4779            {
4780                $anon_map = array('personalized' => self::ANONYMIZE_OFF,
4781                    'anonymize_with_code' => self::ANONYMIZE_ON,
4782                    'anonymize_without_code' => self::ANONYMIZE_FREEACCESS);
4783                $this->setAnonymize($anon_map[$template_settings["anonymization_options"]["value"]]);
4784            }*/
4785
4786            // see #0021719 and ilObjectSurveyGUI::savePropertiesObject
4787            $this->setEvaluationAccess($template_settings["evaluation_access"]["value"]);
4788            $codes = (bool) $template_settings["acc_codes"]["value"];
4789            $anon = (bool) $template_settings["anonymization_options"]["value"];
4790            if (!$anon) {
4791                if (!$codes) {
4792                    $this->setAnonymize(ilObjSurvey::ANONYMIZE_OFF);
4793                } else {
4794                    $this->setAnonymize(ilObjSurvey::ANONYMIZE_CODE_ALL);
4795                }
4796            } else {
4797                if ($codes) {
4798                    $this->setAnonymize(ilObjSurvey::ANONYMIZE_ON);
4799                } else {
4800                    $this->setAnonymize(ilObjSurvey::ANONYMIZE_FREEACCESS);
4801                }
4802
4803                $this->setAnonymousUserList($_POST["anon_list"]);
4804            }
4805
4806
4807
4808            /* other settings: not needed here
4809             * - enabled_end_date
4810             * - enabled_start_date
4811             * - rte_switch
4812             */
4813        }
4814
4815        $this->setTemplate($template_id);
4816        $this->saveToDb();
4817    }
4818
4819    public function updateCode($a_id, $a_email, $a_last_name, $a_first_name, $a_sent)
4820    {
4821        $ilDB = $this->db;
4822
4823        $a_email = trim($a_email);
4824
4825        // :TODO:
4826        if (($a_email && !ilUtil::is_email($a_email)) || $a_email == "") {
4827            return false;
4828        }
4829
4830        $data = array("email" => $a_email,
4831            "lastname" => trim($a_last_name),
4832            "firstname" => trim($a_first_name));
4833
4834        $fields = array(
4835            "externaldata" => array("text", serialize($data)),
4836            "sent" => array("integer", $a_sent)
4837        );
4838
4839        $ilDB->update(
4840            "svy_anonymous",
4841            $fields,
4842            array("anonymous_id" => array("integer", $a_id))
4843        );
4844
4845        return true;
4846    }
4847
4848
4849    //
4850    // 360°
4851    //
4852
4853    public function get360Mode()
4854    {
4855        if ($this->getMode() == ilObjSurvey::MODE_360) {
4856            return true;
4857        }
4858        return false;
4859    }
4860
4861    public function set360SelfEvaluation($a_value)
4862    {
4863        $this->mode_360_self_eval = (bool) $a_value;
4864    }
4865
4866    public function get360SelfEvaluation()
4867    {
4868        return (bool) $this->mode_360_self_eval;
4869    }
4870
4871    public function set360SelfAppraisee($a_value)
4872    {
4873        $this->mode_360_self_appr = (bool) $a_value;
4874    }
4875
4876    public function get360SelfAppraisee()
4877    {
4878        return (bool) $this->mode_360_self_appr;
4879    }
4880
4881    public function set360SelfRaters($a_value)
4882    {
4883        $this->mode_360_self_rate = (bool) $a_value;
4884    }
4885
4886    public function get360SelfRaters()
4887    {
4888        return (bool) $this->mode_360_self_rate;
4889    }
4890
4891    public function set360Results($a_value)
4892    {
4893        $this->mode_360_results = (int) $a_value;
4894    }
4895
4896    public function get360Results()
4897    {
4898        return (int) $this->mode_360_results;
4899    }
4900
4901    public function addAppraisee($a_user_id)
4902    {
4903        global $DIC;
4904
4905        $ilDB = $DIC->database();
4906        $access = $DIC->access();
4907
4908        if (!$this->isAppraisee($a_user_id) &&
4909            $a_user_id != ANONYMOUS_USER_ID) {
4910            $fields = array(
4911                "obj_id" => array("integer", $this->getSurveyId()),
4912                "user_id" => array("integer", $a_user_id)
4913            );
4914            $ilDB->insert("svy_360_appr", $fields);
4915
4916            // send notification and add to desktop
4917            if ($access->checkAccessOfUser($a_user_id, "read", "", $this->getRefId())) {
4918                $this->sendAppraiseeNotification($a_user_id);
4919            }
4920        }
4921    }
4922
4923    /**
4924     * Send appraisee notification
4925     *
4926     * @param int $a_user_id user id
4927     */
4928    public function sendAppraiseeNotification($a_user_id)
4929    {
4930        $ntf = new ilSystemNotification();
4931        $ntf->setLangModules(array("svy", "survey"));
4932        $ntf->setRefId($this->getRefId());
4933        $ntf->setGotoLangId('url');
4934
4935        // user specific language
4936        $lng = $ntf->getUserLanguage($a_user_id);
4937
4938        $ntf->setIntroductionLangId("svy_user_added_360_appraisee_mail");
4939        $subject = str_replace("%1", $this->getTitle(), $lng->txt("svy_user_added_360_appraisee"));
4940
4941        // #10044
4942        $mail = new ilMail(ANONYMOUS_USER_ID);
4943        $mail->enqueue(
4944            ilObjUser::_lookupLogin($a_user_id),
4945            null,
4946            null,
4947            $subject,
4948            $ntf->composeAndGetMessage($a_user_id, null, "read", true),
4949            []
4950        );
4951    }
4952
4953    /**
4954     * Send appraisee notification
4955     *
4956     * @param int $a_user_id user id
4957     */
4958    public function sendAppraiseeCloseNotification($a_user_id)
4959    {
4960        $ntf = new ilSystemNotification();
4961        $ntf->setLangModules(array("svy", "survey"));
4962        $ntf->setRefId($this->getRefId());
4963        $ntf->setGotoLangId('url');
4964
4965        // user specific language
4966        $lng = $ntf->getUserLanguage($a_user_id);
4967
4968        $ntf->setIntroductionLangId("svy_user_added_360_appraisee_close_mail");
4969        $subject = str_replace("%1", $this->getTitle(), $lng->txt("svy_user_added_360_appraisee"));
4970
4971        // #10044
4972        $mail = new ilMail(ANONYMOUS_USER_ID);
4973        $mail->enqueue(
4974            ilObjUser::_lookupLogin($a_user_id),
4975            null,
4976            null,
4977            $subject,
4978            $ntf->composeAndGetMessage($a_user_id, null, "read", true),
4979            []
4980        );
4981    }
4982
4983    /**
4984     * Send rater notification
4985     *
4986     * @param int $a_user_id user id
4987     */
4988    public function sendRaterNotification($a_user_id, $a_appraisee_id)
4989    {
4990        $ntf = new ilSystemNotification();
4991        $ntf->setLangModules(array("svy", "survey"));
4992        $ntf->setRefId($this->getRefId());
4993        $ntf->setGotoLangId('url');
4994
4995        // user specific language
4996        $lng = $ntf->getUserLanguage($a_user_id);
4997
4998        $ntf->setIntroductionLangId("svy_user_added_360_rater_mail");
4999        $subject = str_replace("%1", $this->getTitle(), $lng->txt("svy_user_added_360_rater"));
5000        $ntf->addAdditionalInfo("survey_360_appraisee", ilUserUtil::getNamePresentation($a_appraisee_id, false, false, "", true));
5001
5002        // #10044
5003        $mail = new ilMail(ANONYMOUS_USER_ID);
5004        $mail->enqueue(
5005            ilObjUser::_lookupLogin($a_user_id),
5006            null,
5007            null,
5008            $subject,
5009            $ntf->composeAndGetMessage($a_user_id, null, "read", true),
5010            []
5011        );
5012    }
5013
5014    public function isAppraisee($a_user_id)
5015    {
5016        $ilDB = $this->db;
5017
5018        $set = $ilDB->query("SELECT user_id" .
5019            " FROM svy_360_appr" .
5020            " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
5021            " AND user_id = " . $ilDB->quote($a_user_id, "integer"));
5022        return (bool) $ilDB->numRows($set);
5023    }
5024
5025    public function isAppraiseeClosed($a_user_id)
5026    {
5027        $ilDB = $this->db;
5028
5029        $set = $ilDB->query("SELECT has_closed" .
5030            " FROM svy_360_appr" .
5031            " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
5032            " AND user_id = " . $ilDB->quote($a_user_id, "integer"));
5033        $row = $ilDB->fetchAssoc($set);
5034        return $row["has_closed"];
5035    }
5036
5037    public function deleteAppraisee($a_user_id)
5038    {
5039        $ilDB = $this->db;
5040
5041        $ilDB->manipulate("DELETE FROM svy_360_appr" .
5042            " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
5043            " AND user_id = " . $ilDB->quote($a_user_id, "integer"));
5044
5045        $set = $ilDB->query("SELECT user_id" .
5046            " FROM svy_360_rater" .
5047            " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
5048            " AND appr_id = " . $ilDB->quote($a_user_id, "integer"));
5049        while ($row = $ilDB->fetchAssoc($set)) {
5050            $this->deleteRater($a_user_id, $row["user_id"]);
5051        }
5052        // appraisee will not be part of raters table
5053        if ($this->get360SelfEvaluation()) {
5054            $this->deleteRater($a_user_id, $a_user_id);
5055        }
5056    }
5057
5058    public function getAppraiseesData()
5059    {
5060        $ilDB = $this->db;
5061
5062        $res = array();
5063
5064        $set = $ilDB->query("SELECT * FROM svy_360_appr" .
5065            " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer"));
5066        while ($row = $ilDB->fetchAssoc($set)) {
5067            $name = ilObjUser::_lookupName($row["user_id"]);
5068            $name["email"] = ilObjUser::_lookupEmail($row["user_id"]);
5069            $name["name"] = $name["lastname"] . ", " . $name["firstname"];
5070            $res[$row["user_id"]] = $name;
5071
5072            $finished = 0;
5073            $raters = $this->getRatersData($row["user_id"]);
5074            foreach ($raters as $rater) {
5075                if ($rater["finished"]) {
5076                    $finished++;
5077                }
5078            }
5079            $res[$row["user_id"]]["finished"] = $finished . "/" . sizeof($raters);
5080            $res[$row["user_id"]]["closed"] = $row["has_closed"];
5081        }
5082
5083        return $res;
5084    }
5085
5086    public function addRater($a_appraisee_id, $a_user_id, $a_anonymous_id = 0)
5087    {
5088        global $DIC;
5089
5090        $ilDB = $DIC->database();
5091        $access = $DIC->access();
5092
5093        if ($this->isAppraisee($a_appraisee_id) &&
5094            !$this->isRater($a_appraisee_id, $a_user_id, $a_anonymous_id)) {
5095            $fields = array(
5096                "obj_id" => array("integer", $this->getSurveyId()),
5097                "appr_id" => array("integer", $a_appraisee_id),
5098                "user_id" => array("integer", $a_user_id),
5099                "anonymous_id" => array("integer", $a_anonymous_id)
5100            );
5101            $ilDB->insert("svy_360_rater", $fields);
5102
5103            // send notification and add to desktop
5104            if ($access->checkAccessOfUser($a_user_id, "read", "", $this->getRefId())) {
5105                $this->sendRaterNotification($a_user_id, $a_appraisee_id);
5106            }
5107        }
5108    }
5109
5110    public function isRater($a_appraisee_id, $a_user_id, $a_anonymous_id = 0)
5111    {
5112        $ilDB = $this->db;
5113
5114        // user is rater if already appraisee and active self-evaluation
5115        if ($this->isAppraisee($a_user_id) &&
5116            $this->get360SelfEvaluation() &&
5117            (!$a_appraisee_id || $a_appraisee_id == $a_user_id)) {
5118            return true;
5119        }
5120
5121        // :TODO: should we get rid of code as well?
5122
5123        $sql = "SELECT user_id" .
5124            " FROM svy_360_rater" .
5125            " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
5126            " AND user_id = " . $ilDB->quote($a_user_id, "integer") .
5127            " AND anonymous_id = " . $ilDB->quote($a_anonymous_id, "integer");
5128        if ($a_appraisee_id) {
5129            $sql .= " AND appr_id = " . $ilDB->quote($a_appraisee_id, "integer");
5130        }
5131        $set = $ilDB->query($sql);
5132        return (bool) $ilDB->numRows($set);
5133    }
5134
5135    public function deleteRater($a_appraisee_id, $a_user_id, $a_anonymous_id = 0)
5136    {
5137        $ilDB = $this->db;
5138
5139        $finished_id = $this->getFinishedIdForAppraiseeIdAndRaterId($a_appraisee_id, $a_user_id);
5140        if ($finished_id) {
5141            $this->removeSelectedSurveyResults(array($finished_id));
5142        }
5143
5144        $ilDB->manipulate("DELETE FROM svy_360_rater" .
5145            " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
5146            " AND appr_id = " . $ilDB->quote($a_appraisee_id, "integer") .
5147            " AND user_id = " . $ilDB->quote($a_user_id, "integer") .
5148            " AND anonymous_id = " . $ilDB->quote($a_anonymous_id, "integer"));
5149    }
5150
5151    public function getRatersData($a_appraisee_id)
5152    {
5153        $ilDB = $this->db;
5154
5155        $res = $anonymous_ids = array();
5156
5157        $set = $ilDB->query("SELECT * FROM svy_360_rater" .
5158            " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
5159            " AND appr_id = " . $ilDB->quote($a_appraisee_id, "integer"));
5160        while ($row = $ilDB->fetchAssoc($set)) {
5161            if ($row["anonymous_id"]) {
5162                $res["a" . $row["anonymous_id"]] = array(
5163                    "lastname" => "unknown code " . $row["anonymous_id"],
5164                    "sent" => $row["mail_sent"],
5165                    "finished" => null
5166                );
5167                $anonymous_ids[] = $row["anonymous_id"];
5168            } else {
5169                $name = ilObjUser::_lookupName($row["user_id"]);
5170                $name["name"] = $name["lastname"] . ", " . $name["firstname"];
5171                $name["user_id"] = "u" . $name["user_id"];
5172                $name["email"] = ilObjUser::_lookupEmail($row["user_id"]);
5173                $name["sent"] = $row["mail_sent"];
5174                $name["finished"] = (bool) $this->is360SurveyStarted($a_appraisee_id, $row["user_id"]);
5175                $res["u" . $row["user_id"]] = $name;
5176            }
5177        }
5178
5179        if (sizeof($anonymous_ids)) {
5180            $data = $this->getSurveyCodesTableData($anonymous_ids);
5181            foreach ($data as $item) {
5182                if (isset($res["a" . $item["id"]])) {
5183                    $res["a" . $item["id"]] = array(
5184                        "user_id" => "a" . $item["id"],
5185                        "lastname" => $item["last_name"],
5186                        "firstname" => $item["first_name"],
5187                        "name" => $item["last_name"] . ", " . $item["first_name"],
5188                        "login" => "",
5189                        "email" => $item["email"],
5190                        "code" => $item["code"],
5191                        "href" => $item["href"],
5192                        "sent" => $res["a" . $item["id"]]["sent"],
5193                        "finished" => (bool) $this->is360SurveyStarted($a_appraisee_id, null, $item["code"])
5194                    );
5195                }
5196            }
5197        }
5198
5199        return $res;
5200    }
5201
5202    public function getAppraiseesToRate($a_user_id, $a_anonymous_id = null)
5203    {
5204        $ilDB = $this->db;
5205
5206        $res = array();
5207
5208        $sql = "SELECT appr_id FROM svy_360_rater" .
5209            " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer");
5210
5211        if ($a_user_id) {
5212            $sql .= " AND user_id = " . $ilDB->quote($a_user_id, "integer");
5213        } else {
5214            $sql .= " AND anonymous_id = " . $ilDB->quote($a_anonymous_id, "integer");
5215        }
5216
5217        $set = $ilDB->query($sql);
5218        while ($row = $ilDB->fetchAssoc($set)) {
5219            $res[] = $row["appr_id"];
5220        }
5221
5222        // user may evaluate himself if already appraisee
5223        if ($this->get360SelfEvaluation() &&
5224            $this->isAppraisee($a_user_id) &&
5225            !in_array($a_user_id, $res)) {
5226            $res[] = $a_user_id;
5227        }
5228
5229        return $res;
5230    }
5231
5232    public function getAnonymousIdByCode($a_code)
5233    {
5234        $ilDB = $this->db;
5235
5236        $set = $ilDB->query("SELECT anonymous_id FROM svy_anonymous" .
5237                " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
5238                " AND survey_key = " . $ilDB->quote($a_code, "text"));
5239        $res = $ilDB->fetchAssoc($set);
5240        return $res["anonymous_id"];
5241    }
5242
5243    public function is360SurveyStarted($appr_id, $user_id, $anonymous_code = null)
5244    {
5245        $ilDB = $this->db;
5246
5247        $sql = "SELECT * FROM svy_finished" .
5248            " WHERE survey_fi =" . $ilDB->quote($this->getSurveyId(), "integer") .
5249            " AND appr_id = " . $ilDB->quote($appr_id, "integer");
5250        if ($user_id) {
5251            $sql .= " AND user_fi = " . $ilDB->quote($user_id, "integer");
5252        } else {
5253            $sql .= " AND anonymous_id = " . $ilDB->quote($anonymous_code, "text");
5254        }
5255        $result = $ilDB->query($sql);
5256        if ($result->numRows() == 0) {
5257            return false;
5258        } else {
5259            $row = $ilDB->fetchAssoc($result);
5260            return (int) $row["state"];
5261        }
5262    }
5263
5264    public function getUserSurveyExecutionStatus($a_code = null)
5265    {
5266        $ilUser = $this->user;
5267        $ilDB = $this->db;
5268
5269        $user_id = $ilUser->getId();
5270
5271        // code is obligatory?
5272        if (!$this->isAccessibleWithoutCode()) {
5273            if (!$a_code) {
5274                // registered raters do not need code
5275                if ($this->get360Mode() &&
5276                    $user_id != ANONYMOUS_USER_ID &&
5277                    $this->isRater(0, $user_id)) {
5278                    // auto-generate code
5279                    $a_code = $this->createNewAccessCode();
5280                    $this->saveUserAccessCode($user_id, $a_code);
5281                } else {
5282                    return null;
5283                }
5284            }
5285        } elseif ($user_id == ANONYMOUS_USER_ID ||
5286            $this->getAnonymize() == self::ANONYMIZE_FREEACCESS) {
5287            if (!$a_code) {
5288                // auto-generate code
5289                $a_code = $this->createNewAccessCode();
5290                $this->saveUserAccessCode($user_id, $a_code);
5291            }
5292        } else {
5293            $a_code = null;
5294        }
5295
5296        $res = array();
5297
5298        $sql = "SELECT * FROM svy_finished" .
5299            " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer");
5300        // if proper user id is given, use it or current code
5301        if ($user_id != ANONYMOUS_USER_ID) {
5302            $sql .= " AND (user_fi = " . $ilDB->quote($user_id, "integer") .
5303                " OR anonymous_id = " . $ilDB->quote($a_code, "text") . ")";
5304        }
5305        // use anonymous code to find finished id(s)
5306        else {
5307            $sql .= " AND anonymous_id = " . $ilDB->quote($a_code, "text");
5308        }
5309        $set = $ilDB->query($sql);
5310        while ($row = $ilDB->fetchAssoc($set)) {
5311            $res[$row["finished_id"]] = array("appr_id" => $row["appr_id"],
5312                "user_id" => $row["user_fi"],
5313                "code" => $row["anonymous_id"],
5314                "finished" => (bool) $row["state"]);
5315        }
5316
5317        return array("code" => $a_code, "runs" => $res);
5318    }
5319
5320    public function findCodeForUser($a_user_id)
5321    {
5322        $ilDB = $this->db;
5323
5324        if ($a_user_id != ANONYMOUS_USER_ID) {
5325            $set = $ilDB->query("SELECT sf.anonymous_id FROM svy_finished sf" .
5326                " WHERE sf.survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
5327                " AND sf.user_fi = " . $ilDB->quote($a_user_id, "integer"));
5328            $a_code = $ilDB->fetchAssoc($set);
5329            return $a_code["anonymous_id"];
5330        }
5331    }
5332
5333    public function isUnusedCode($a_code, $a_user_id)
5334    {
5335        $ilDB = $this->db;
5336
5337        $set = $ilDB->query("SELECT user_fi FROM svy_finished" .
5338            " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
5339            " AND anonymous_id = " . $ilDB->quote($a_code, "text"));
5340        $user_id = $ilDB->fetchAssoc($set);
5341        $user_id = $user_id["user_fi"];
5342
5343        if ($user_id && ($user_id != $a_user_id || $user_id == ANONYMOUS_USER_ID)) {
5344            return false;
5345        }
5346        return true;
5347    }
5348
5349    public function getFinishedIdsForAppraiseeId($a_appr_id, $a_exclude_appraisee = false)
5350    {
5351        $ilDB = $this->db;
5352
5353        $res = array();
5354
5355        $set = $ilDB->query("SELECT finished_id, user_fi FROM svy_finished" .
5356            " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
5357            " AND appr_id = " . $ilDB->quote($a_appr_id, "integer"));
5358        while ($row = $ilDB->fetchAssoc($set)) {
5359            if ($a_exclude_appraisee && $row["user_fi"] == $a_appr_id) {
5360                continue;
5361            }
5362            $res[] = $row["finished_id"];
5363        }
5364
5365        return $res;
5366    }
5367
5368    /**
5369     * Get finished id for an appraisee and a rater
5370     *
5371     * @param int $a_appraisee_id appraisee id
5372     * @param int $a_rater_id rater id
5373     * @return int finished id
5374     */
5375    public function getFinishedIdForAppraiseeIdAndRaterId($a_appr_id, $a_rat_id)
5376    {
5377        $ilDB = $this->db;
5378
5379        $set = $ilDB->query("SELECT finished_id, user_fi FROM svy_finished" .
5380            " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
5381            " AND appr_id = " . $ilDB->quote($a_appr_id, "integer") .
5382            " AND user_fi = " . $ilDB->quote($a_rat_id, "integer"));
5383        $row = $ilDB->fetchAssoc($set);
5384        return $row["finished_id"];
5385    }
5386
5387
5388    // 360° using competence/skill service
5389
5390    /**
5391     * Set skill service
5392     *
5393     * @param bool $a_val activate skill service
5394     */
5395    public function setSkillService($a_val)
5396    {
5397        $this->mode_skill_service = $a_val;
5398    }
5399
5400    /**
5401     * Get skill service
5402     *
5403     * @return bool activate skill service
5404     */
5405    public function getSkillService()
5406    {
5407        return $this->mode_skill_service;
5408    }
5409
5410    public function set360RaterSent($a_appraisee_id, $a_user_id, $a_anonymous_id, $a_tstamp = null)
5411    {
5412        $ilDB = $this->db;
5413
5414        if (!$a_tstamp) {
5415            $a_tstamp = time();
5416        }
5417
5418        $ilDB->manipulate("UPDATE svy_360_rater" .
5419            " SET mail_sent = " . $ilDB->quote($a_tstamp, "integer") .
5420            " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
5421            " AND appr_id = " . $ilDB->quote($a_appraisee_id, "integer") .
5422            " AND user_id = " . $ilDB->quote($a_user_id, "integer") .
5423            " AND anonymous_id = " . $ilDB->quote($a_anonymous_id, "integer"));
5424    }
5425
5426    public function closeAppraisee($a_user_id)
5427    {
5428        global $DIC;
5429
5430        $ilDB = $DIC->database();
5431        $user = $DIC->user();
5432
5433        // close the appraisee
5434        $ilDB->manipulate("UPDATE svy_360_appr" .
5435            " SET has_closed = " . $ilDB->quote(time(), "integer") .
5436            " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer") .
5437            " AND user_id = " . $ilDB->quote($a_user_id, "integer"));
5438
5439        // write competences
5440        $skmg_set = new ilSkillManagementSettings();
5441        if ($this->getSkillService() && $skmg_set->isActivated()) {
5442            $sskill = new ilSurveySkill($this);
5443            $sskill->writeAndAddAppraiseeSkills($a_user_id);
5444        }
5445
5446        // send notification
5447        if ($user->getId() != $a_user_id) {
5448            $this->sendAppraiseeCloseNotification($a_user_id);
5449        }
5450    }
5451
5452    public function openAllAppraisees()
5453    {
5454        $ilDB = $this->db;
5455
5456        $ilDB->manipulate("UPDATE svy_360_appr" .
5457            " SET has_closed = " . $ilDB->quote(null, "integer") .
5458            " WHERE obj_id = " . $ilDB->quote($this->getSurveyId(), "integer"));
5459    }
5460
5461    public static function validateExternalRaterCode($a_ref_id, $a_code)
5462    {
5463        if (!isset($_SESSION["360_extrtr"][$a_ref_id])) {
5464            $svy = new self($a_ref_id);
5465            $svy->loadFromDB();
5466
5467            if ($svy->canStartSurvey(null, true) &&
5468                $svy->get360Mode() &&
5469                $svy->isAnonymousKey($a_code)) {
5470                $anonymous_id = $svy->getAnonymousIdByCode($a_code);
5471                if ($anonymous_id) {
5472                    if (sizeof($svy->getAppraiseesToRate(null, $anonymous_id))) {
5473                        $_SESSION["360_extrtr"][$a_ref_id] = true;
5474                        return true;
5475                    }
5476                }
5477            }
5478
5479            $_SESSION["360_extrtr"][$a_ref_id] = false;
5480            return false;
5481        }
5482
5483        return $_SESSION["360_extrtr"][$a_ref_id];
5484    }
5485
5486
5487    //
5488    // reminder/notification
5489    //
5490
5491    public function getReminderStatus()
5492    {
5493        return (bool) $this->reminder_status;
5494    }
5495
5496    public function setReminderStatus($a_value)
5497    {
5498        $this->reminder_status = (bool) $a_value;
5499    }
5500
5501    public function getReminderStart()
5502    {
5503        return $this->reminder_start;
5504    }
5505
5506    public function setReminderStart(ilDate $a_value = null)
5507    {
5508        $this->reminder_start = $a_value;
5509    }
5510
5511    public function getReminderEnd()
5512    {
5513        return $this->reminder_end;
5514    }
5515
5516    public function setReminderEnd(ilDate $a_value = null)
5517    {
5518        $this->reminder_end = $a_value;
5519    }
5520
5521    public function getReminderFrequency()
5522    {
5523        return $this->reminder_frequency;
5524    }
5525
5526    public function setReminderFrequency($a_value)
5527    {
5528        $this->reminder_frequency = (int) $a_value;
5529    }
5530
5531    public function getReminderTarget()
5532    {
5533        return $this->reminder_target;
5534    }
5535
5536    public function setReminderTarget($a_value)
5537    {
5538        $this->reminder_target = (int) $a_value;
5539    }
5540
5541    public function getReminderLastSent()
5542    {
5543        return $this->reminder_last_sent;
5544    }
5545
5546    public function setReminderLastSent($a_value)
5547    {
5548        $this->reminder_last_sent = $a_value;
5549    }
5550
5551    /**
5552     * @param bool $selectDefault
5553     * @return mixed
5554     */
5555    public function getReminderTemplate($selectDefault = false)
5556    {
5557        if ($selectDefault) {
5558            $defaultTemplateId = 0;
5559            $this->getReminderMailTemplates($defaultTemplateId);
5560
5561            if ($defaultTemplateId > 0) {
5562                return $defaultTemplateId;
5563            }
5564        }
5565
5566        return $this->reminder_tmpl;
5567    }
5568
5569    public function setReminderTemplate($a_value)
5570    {
5571        $this->reminder_tmpl = $a_value;
5572    }
5573
5574    public function getTutorNotificationStatus()
5575    {
5576        return (bool) $this->tutor_ntf_status;
5577    }
5578
5579    public function setTutorNotificationStatus($a_value)
5580    {
5581        $this->tutor_ntf_status = (bool) $a_value;
5582    }
5583
5584    public function getTutorNotificationRecipients()
5585    {
5586        return $this->tutor_ntf_recipients;
5587    }
5588
5589    public function setTutorNotificationRecipients(array $a_value)
5590    {
5591        $this->tutor_ntf_recipients = $a_value;
5592    }
5593
5594    public function getTutorNotificationTarget()
5595    {
5596        return $this->tutor_ntf_target;
5597    }
5598
5599    public function setTutorNotificationTarget($a_value)
5600    {
5601        $this->tutor_ntf_target = (int) $a_value;
5602    }
5603
5604    public function getTutorResultsStatus()
5605    {
5606        return (bool) $this->tutor_res_status;
5607    }
5608
5609    public function setTutorResultsStatus($a_value)
5610    {
5611        $this->tutor_res_status = (bool) $a_value;
5612    }
5613
5614    public function getTutorResultsRecipients()
5615    {
5616        return $this->tutor_res_recipients;
5617    }
5618
5619    public function setTutorResultsRecipients(array $a_value)
5620    {
5621        $this->tutor_res_recipients = $a_value;
5622    }
5623
5624    protected function checkTutorNotification()
5625    {
5626        $ilDB = $this->db;
5627
5628        if ($this->getTutorNotificationStatus()) {
5629            $user_ids = $this->getNotificationTargetUserIds(($this->getTutorNotificationTarget() == self::NOTIFICATION_INVITED_USERS));
5630            if ($user_ids) {
5631                $set = $ilDB->query("SELECT COUNT(*) numall FROM svy_finished" .
5632                    " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
5633                    " AND state = " . $ilDB->quote(1, "integer") .
5634                    " AND " . $ilDB->in("user_fi", $user_ids, "", "integer"));
5635                $row = $ilDB->fetchAssoc($set);
5636                if ($row["numall"] == sizeof($user_ids)) {
5637                    $this->sendTutorNotification();
5638                }
5639            }
5640        }
5641    }
5642
5643    /**
5644     * Send 360 reminders
5645     *
5646     * @param
5647     * @return
5648     */
5649    public function sent360Reminders()
5650    {
5651        global $DIC;
5652
5653        $access = $DIC->access();
5654
5655        // collect all open ratings
5656        $rater_ids = array();
5657        foreach ($this->getAppraiseesData() as $app) {
5658            $this->log->debug("Handle appraisee " . $app['user_id']);
5659
5660            if (!$this->isAppraiseeClosed($app['user_id'])) {
5661                $this->log->debug("Check self evaluation, self: " . $this->get360SelfAppraisee() . ", target: " . $this->getReminderTarget());
5662
5663                // self evaluation?
5664                if ($this->get360SelfEvaluation() &&
5665                    in_array($this->getReminderTarget(), array(ilObjSurvey::NOTIFICATION_APPRAISEES, ilObjSurvey::NOTIFICATION_APPRAISEES_AND_RATERS))) {
5666                    $this->log->debug("...1");
5667                    // did user already finished self evaluation?
5668                    if (!$this->is360SurveyStarted($app['user_id'], $app['user_id'])) {
5669                        $this->log->debug("...2");
5670                        if (!is_array($rater_ids[$app['user_id']])) {
5671                            $rater_ids[$app['user_id']] = array();
5672                        }
5673                        if (!in_array($app["user_id"], $rater_ids[$app['user_id']])) {
5674                            $rater_ids[$app['user_id']][] = $app["user_id"];
5675                        }
5676                    }
5677                }
5678
5679                $this->log->debug("Check raters.");
5680
5681                // should raters be notified?
5682                if (in_array($this->getReminderTarget(), array(ilObjSurvey::NOTIFICATION_RATERS, ilObjSurvey::NOTIFICATION_APPRAISEES_AND_RATERS))) {
5683                    foreach ($this->getRatersData($app['user_id']) as $rater) {
5684                        // is rater not anonymous and did not rate yet?
5685                        if (!$rater["anonymous_id"] && !$rater["finished"]) {
5686                            if (!is_array($rater_ids[$rater["user_id"]])) {
5687                                $rater_ids[$rater["user_id"]] = array();
5688                            }
5689                            if (!in_array($app["user_id"], $rater_ids[$rater["user_id"]])) {
5690                                $rater_ids[$rater["user_id"]][] = $app["user_id"];
5691                            }
5692                        }
5693                    }
5694                }
5695            }
5696        }
5697
5698        $this->log->debug("Found raters:" . count($rater_ids));
5699
5700        foreach ($rater_ids as $id => $app) {
5701            if ($access->checkAccessOfUser($id, "read", "", $this->getRefId())) {
5702                $this->send360ReminderToUser($id, $app);
5703            }
5704        }
5705    }
5706
5707    /**
5708     * Send rater notification
5709     *
5710     * @param int $a_user_id user id
5711     */
5712    public function send360ReminderToUser($a_user_id, $a_appraisee_ids)
5713    {
5714        $this->log->debug("Send mail to:" . $a_user_id);
5715
5716        $ntf = new ilSystemNotification();
5717        $ntf->setLangModules(array("svy", "survey"));
5718        $ntf->setRefId($this->getRefId());
5719        $ntf->setGotoLangId('url');
5720
5721        // user specific language
5722        $lng = $ntf->getUserLanguage($a_user_id);
5723
5724        $ntf->setIntroductionLangId("svy_user_added_360_rater_reminder_mail");
5725        $subject = str_replace("%1", $this->getTitle(), $lng->txt("svy_user_added_360_rater"));
5726
5727        foreach ($a_appraisee_ids as $appraisee_id) {
5728            $ntf->addAdditionalInfo("survey_360_appraisee", ilUserUtil::getNamePresentation($appraisee_id, false, false, "", true));
5729        }
5730
5731        // #10044
5732        $mail = new ilMail(ANONYMOUS_USER_ID);
5733        $mail->enqueue(
5734            ilObjUser::_lookupLogin($a_user_id),
5735            null,
5736            null,
5737            $subject,
5738            $ntf->composeAndGetMessage($a_user_id, null, "read", true),
5739            []
5740        );
5741    }
5742
5743
5744    public function getNotificationTargetUserIds($a_use_invited)
5745    {
5746        $tree = $this->tree;
5747
5748        if ((bool) $a_use_invited) {
5749            $user_ids = $this->invitation_manager->getAllForSurvey($this->getSurveyId());
5750        } else {
5751            $parent_grp_ref_id = $tree->checkForParentType($this->getRefId(), "grp");
5752            if ($parent_grp_ref_id) {
5753                $part = new ilGroupParticipants(ilObject::_lookupObjId($parent_grp_ref_id));
5754                $user_ids = $part->getMembers();
5755            } else {
5756                $parent_crs_ref_id = $tree->checkForParentType($this->getRefId(), "crs");
5757                if ($parent_crs_ref_id) {
5758                    $part = new ilCourseParticipants(ilObject::_lookupObjId($parent_crs_ref_id));
5759                    $user_ids = $part->getMembers();
5760                }
5761            }
5762        }
5763        return $user_ids;
5764    }
5765
5766    protected function sendTutorNotification()
5767    {
5768        $link = ilLink::_getStaticLink($this->getRefId(), "svy");
5769
5770        foreach ($this->getTutorNotificationRecipients() as $user_id) {
5771            // use language of recipient to compose message
5772            $ulng = ilLanguageFactory::_getLanguageOfUser($user_id);
5773            $ulng->loadLanguageModule('survey');
5774
5775            $subject = sprintf($ulng->txt('survey_notification_tutor_subject'), $this->getTitle());
5776            $message = sprintf($ulng->txt('survey_notification_tutor_salutation'), ilObjUser::_lookupFullname($user_id)) . "\n\n";
5777
5778            $message .= $ulng->txt('survey_notification_tutor_body') . ":\n\n";
5779            $message .= $ulng->txt('obj_svy') . ": " . $this->getTitle() . "\n";
5780            $message .= "\n" . $ulng->txt('survey_notification_tutor_link') . ": " . $link;
5781
5782            $mail_obj = new ilMail(ANONYMOUS_USER_ID);
5783            $mail_obj->appendInstallationSignature(true);
5784            $mail_obj->enqueue(
5785                ilObjUser::_lookupLogin($user_id),
5786                "",
5787                "",
5788                $subject,
5789                $message,
5790                array()
5791            );
5792        }
5793    }
5794
5795    public function checkReminder()
5796    {
5797        $ilDB = $this->db;
5798        $ilAccess = $this->access;
5799
5800        $now = time();
5801        $now_with_format = date("YmdHis", $now);
5802        $today = date("Y-m-d");
5803
5804        $this->log->debug("Check status and dates.");
5805
5806        // object settings / participation period
5807        if (
5808            $this->getOfflineStatus() ||
5809            !$this->getReminderStatus() ||
5810            ($this->getStartDate() && $now_with_format < $this->getStartDate()) ||
5811            ($this->getEndDate() && $now_with_format > $this->getEndDate())) {
5812            return false;
5813        }
5814
5815        // reminder period
5816        $start = $this->getReminderStart();
5817        if ($start) {
5818            $start = $start->get(IL_CAL_DATE);
5819        }
5820        $end = $this->getReminderEnd();
5821        if ($end) {
5822            $end = $end->get(IL_CAL_DATE);
5823        }
5824        if ($today < $start ||
5825            ($end && $today > $end)) {
5826            return false;
5827        }
5828
5829        $this->log->debug("Check access period.");
5830
5831        // object access period
5832        $item_data = ilObjectActivation::getItem($this->getRefId());
5833        if ($item_data["timing_type"] == ilObjectActivation::TIMINGS_ACTIVATION &&
5834            ($now < $item_data["timing_start"] ||
5835            $now > $item_data["timing_end"])) {
5836            return false;
5837        }
5838
5839        $this->log->debug("Check frequency.");
5840
5841        // check frequency
5842        $cut = new ilDate($today, IL_CAL_DATE);
5843        $cut->increment(IL_CAL_DAY, $this->getReminderFrequency() * -1);
5844        if (!$this->getReminderLastSent() ||
5845            $cut->get(IL_CAL_DATE) >= substr($this->getReminderLastSent(), 0, 10)) {
5846            $missing_ids = array();
5847
5848            if (!$this->get360Mode()) {
5849                $this->log->debug("Entering survey mode.");
5850
5851                // #16871
5852                $user_ids = $this->getNotificationTargetUserIds(($this->getReminderTarget() == self::NOTIFICATION_INVITED_USERS));
5853                if ($user_ids) {
5854                    // gather participants who already finished
5855                    $finished_ids = array();
5856                    $set = $ilDB->query("SELECT user_fi FROM svy_finished" .
5857                        " WHERE survey_fi = " . $ilDB->quote($this->getSurveyId(), "integer") .
5858                        " AND state = " . $ilDB->quote(1, "text") .
5859                        " AND " . $ilDB->in("user_fi", $user_ids, "", "integer"));
5860                    while ($row = $ilDB->fetchAssoc($set)) {
5861                        $finished_ids[] = $row["user_fi"];
5862                    }
5863
5864                    // some users missing out?
5865                    $missing_ids = array_diff($user_ids, $finished_ids);
5866                    if ($missing_ids) {
5867                        foreach ($missing_ids as $idx => $user_id) {
5868                            // should be able to participate
5869                            if (!$ilAccess->checkAccessOfUser($user_id, "read", "", $this->getRefId(), "svy", $this->getId())) {
5870                                unset($missing_ids[$idx]);
5871                            }
5872                        }
5873                    }
5874                    if ($missing_ids) {
5875                        $this->sentReminder($missing_ids);
5876                    }
5877                }
5878            } else {
5879                $this->log->debug("Entering 360 mode.");
5880
5881                $this->sent360Reminders();
5882            }
5883
5884
5885            $this->setReminderLastSent($today);
5886            $this->saveToDb();
5887
5888            return sizeof($missing_ids);
5889        }
5890
5891        return false;
5892    }
5893
5894    protected function sentReminder(array $a_recipient_ids)
5895    {
5896        global $DIC;
5897
5898        // use mail template
5899        if ($this->getReminderTemplate() &&
5900            array_key_exists($this->getReminderTemplate(), $this->getReminderMailTemplates())) {
5901            /** @var \ilMailTemplateService $templateService */
5902            $templateService = $DIC['mail.texttemplates.service'];
5903            $tmpl = $templateService->loadTemplateForId((int) $this->getReminderTemplate());
5904
5905            $tmpl_params = array(
5906                "ref_id" => $this->getRefId(),
5907                "ts" => time()
5908            );
5909        } else {
5910            $tmpl = null;
5911            $link = ilLink::_getStaticLink($this->getRefId(), "svy");
5912        }
5913
5914        foreach ($a_recipient_ids as $user_id) {
5915            if ($tmpl) {
5916                $subject = $tmpl->getSubject();
5917                $message = $this->sentReminderPlaceholders($tmpl->getMessage(), $user_id, $tmpl_params);
5918            }
5919            // use lng
5920            else {
5921                // use language of recipient to compose message
5922                $ulng = ilLanguageFactory::_getLanguageOfUser($user_id);
5923                $ulng->loadLanguageModule('survey');
5924
5925                $subject = sprintf($ulng->txt('survey_reminder_subject'), $this->getTitle());
5926                $message = sprintf($ulng->txt('survey_reminder_salutation'), ilObjUser::_lookupFullname($user_id)) . "\n\n";
5927
5928                $message .= $ulng->txt('survey_reminder_body') . ":\n\n";
5929                $message .= $ulng->txt('obj_svy') . ": " . $this->getTitle() . "\n";
5930                $message .= "\n" . $ulng->txt('survey_reminder_link') . ": " . $link;
5931            }
5932
5933            $mail_obj = new ilMail(ANONYMOUS_USER_ID);
5934            $mail_obj->appendInstallationSignature(true);
5935            $mail_obj->enqueue(
5936                ilObjUser::_lookupLogin($user_id),
5937                "",
5938                "",
5939                $subject,
5940                $message,
5941                array()
5942            );
5943        }
5944    }
5945
5946    public function setActivationStartDate($starting_time = null)
5947    {
5948        $this->activation_starting_time = $starting_time;
5949    }
5950
5951    public function setActivationEndDate($ending_time = null)
5952    {
5953        $this->activation_ending_time = $ending_time;
5954    }
5955
5956    public function getActivationStartDate()
5957    {
5958        return (strlen($this->activation_starting_time)) ? $this->activation_starting_time : null;
5959    }
5960
5961    public function getActivationEndDate()
5962    {
5963        return (strlen($this->activation_ending_time)) ? $this->activation_ending_time : null;
5964    }
5965
5966    public function setViewOwnResults($a_value)
5967    {
5968        $this->view_own_results = (bool) $a_value;
5969    }
5970
5971    public function hasViewOwnResults()
5972    {
5973        return $this->view_own_results;
5974    }
5975
5976    public function setMailOwnResults($a_value)
5977    {
5978        $this->mail_own_results = (bool) $a_value;
5979    }
5980
5981    public function hasMailOwnResults()
5982    {
5983        return $this->mail_own_results;
5984    }
5985
5986    public function setMailConfirmation($a_value)
5987    {
5988        $this->mail_confirmation = (bool) $a_value;
5989    }
5990
5991    public function hasMailConfirmation()
5992    {
5993        return $this->mail_confirmation;
5994    }
5995
5996    public function setAnonymousUserList($a_value)
5997    {
5998        $this->anon_user_list = (bool) $a_value;
5999    }
6000
6001    public function hasAnonymousUserList()
6002    {
6003        return $this->anon_user_list;
6004    }
6005
6006    public static function getSurveySkippedValue()
6007    {
6008        global $DIC;
6009
6010        $lng = $DIC->language();
6011
6012        // #13541
6013
6014        $surveySetting = new ilSetting("survey");
6015        if (!$surveySetting->get("skipped_is_custom", false)) {
6016            return $lng->txt("skipped");
6017        } else {
6018            return $surveySetting->get("skipped_custom_value", "");
6019        }
6020    }
6021
6022    /**
6023     * @param int $defaultTemplateId
6024     * @return array
6025     */
6026    public function getReminderMailTemplates(&$defaultTemplateId = null)
6027    {
6028        global $DIC;
6029
6030        $res = array();
6031
6032        /** @var \ilMailTemplateService $templateService */
6033        $templateService = $DIC['mail.texttemplates.service'];
6034        foreach ($templateService->loadTemplatesForContextId((string) ilSurveyMailTemplateReminderContext::ID) as $tmpl) {
6035            $res[$tmpl->getTplId()] = $tmpl->getTitle();
6036            if (null !== $defaultTemplateId && $tmpl->isDefault()) {
6037                $defaultTemplateId = $tmpl->getTplId();
6038            }
6039        }
6040
6041        return $res;
6042    }
6043
6044    protected function sentReminderPlaceholders($a_message, $a_user_id, array $a_context_params)
6045    {
6046        // see ilMail::replacePlaceholders()
6047        try {
6048            $context = \ilMailTemplateContextService::getTemplateContextById(ilSurveyMailTemplateReminderContext::ID);
6049
6050            $user = new \ilObjUser($a_user_id);
6051
6052            $processor = new \ilMailTemplatePlaceholderResolver($context, $a_message);
6053            $a_message = $processor->resolve($user, $a_context_params);
6054        } catch (\Exception $e) {
6055            ilLoggerFactory::getLogger('mail')->error(__METHOD__ . ' has been called with invalid context.');
6056        }
6057
6058        return $a_message;
6059    }
6060
6061    public function setMode($a_value)
6062    {
6063        $this->mode = $a_value;
6064    }
6065
6066    public function getMode()
6067    {
6068        return $this->mode;
6069    }
6070
6071    public function setSelfEvaluationResults($a_value)
6072    {
6073        $this->mode_self_eval_results = $a_value;
6074    }
6075
6076    public function getSelfEvaluationResults()
6077    {
6078        return $this->mode_self_eval_results;
6079    }
6080
6081    public static function getSurveysWithTutorResults()
6082    {
6083        global $ilDB;
6084
6085        $res = array();
6086
6087        $log = ilLoggerFactory::getLogger("svy");
6088
6089
6090        $q = "SELECT obj_fi FROM svy_svy" .
6091            " WHERE tutor_res_cron IS NULL" .
6092            " AND tutor_res_status = " . $ilDB->quote(1, "integer") .
6093            " AND enddate < " . $ilDB->quote(date("Ymd000000"), "text");
6094
6095        if (DEVMODE) {
6096            $q = "SELECT obj_fi FROM svy_svy" .
6097                " WHERE tutor_res_status = " . $ilDB->quote(1, "integer") .
6098                " AND enddate < " . $ilDB->quote(date("Ymd000000"), "text");
6099        }
6100
6101        $set = $ilDB->query($q);
6102
6103        $log->debug($q);
6104
6105        while ($row = $ilDB->fetchAssoc($set)) {
6106            $res[] = $row["obj_fi"];
6107        }
6108
6109        return $res;
6110    }
6111
6112    public function sendTutorResults()
6113    {
6114        global $ilCtrl, $ilDB;
6115
6116        $log = ilLoggerFactory::getLogger("svy");
6117
6118        include_once "./Services/Mail/classes/class.ilMail.php";
6119        include_once "./Services/User/classes/class.ilObjUser.php";
6120        include_once "./Services/Language/classes/class.ilLanguageFactory.php";
6121        include_once "./Services/User/classes/class.ilUserUtil.php";
6122
6123        include_once "./Services/Link/classes/class.ilLink.php";
6124        $link = ilLink::_getStaticLink($this->getRefId(), "svy");
6125
6126        // somehow needed in cron-calls
6127        //$ilCtrl->setTargetScript("ilias.php");
6128        //$ilCtrl->initBaseClass("ilobjsurveygui");
6129
6130        // yeah, I know...
6131        $old_ref_id = $_GET["ref_id"];
6132        $old_base_class = $_GET["baseClass"];
6133        $_GET["ref_id"] = $this->getRefId();
6134        $ilCtrl->setTargetScript("ilias.php");
6135        $_GET["baseClass"] = "ilObjSurveyGUI";
6136
6137        $ilCtrl->setParameterByClass("ilSurveyEvaluationGUI", "ref_id", $this->getRefId());
6138
6139        try {
6140            $gui = new ilSurveyEvaluationGUI($this);
6141            $html = $gui->evaluation(1, true, true);
6142        } catch (Exception $exception) {
6143            $_GET["ref_id"] = $old_ref_id;
6144            $_GET["baseClass"] = $old_base_class;
6145            throw $exception;
6146        }
6147        $_GET["ref_id"] = $old_ref_id;
6148        $_GET["baseClass"] = $old_base_class;
6149
6150        $html = preg_replace("/\?dummy\=[0-9]+/", "", $html);
6151        $html = preg_replace("/\?vers\=[0-9A-Za-z\-]+/", "", $html);
6152        $html = str_replace('.css$Id$', ".css", $html);
6153        $html = preg_replace("/src=\"\\.\\//ims", "src=\"" . ILIAS_HTTP_PATH . "/", $html);
6154        $html = preg_replace("/href=\"\\.\\//ims", "href=\"" . ILIAS_HTTP_PATH . "/", $html);
6155
6156        $pdf_factory = new ilHtmlToPdfTransformerFactory();
6157        $pdf = $pdf_factory->deliverPDFFromHTMLString($html, "survey.pdf", ilHtmlToPdfTransformerFactory::PDF_OUTPUT_FILE, "Survey", "Results");
6158
6159        /*
6160        $log->debug("calling phantom for ref_id: " . $this->getRefId());
6161
6162        $pdf = $gui->callPdfGeneration($url, "pdf", true, true);
6163
6164        $log->debug("phantom called : " . $pdf);*/
6165
6166        if (!$pdf ||
6167            !file_exists($pdf)) {
6168            return false;
6169        }
6170
6171        // prepare mail attachment
6172        require_once 'Services/Mail/classes/class.ilFileDataMail.php';
6173        $att = "survey_" . $this->getRefId() . ".pdf";
6174        $mail_data = new ilFileDataMail(ANONYMOUS_USER_ID);
6175        $mail_data->copyAttachmentFile($pdf, $att);
6176
6177        foreach ($this->getTutorResultsRecipients() as $user_id) {
6178            // use language of recipient to compose message
6179            $ulng = ilLanguageFactory::_getLanguageOfUser($user_id);
6180            $ulng->loadLanguageModule('survey');
6181
6182            $subject = sprintf($ulng->txt('survey_results_tutor_subject'), $this->getTitle());
6183            $message = sprintf($ulng->txt('survey_notification_tutor_salutation'), ilObjUser::_lookupFullname($user_id)) . "\n\n";
6184
6185            $message .= $ulng->txt('survey_results_tutor_body') . ":\n\n";
6186            $message .= $ulng->txt('obj_svy') . ": " . $this->getTitle() . "\n";
6187            $message .= "\n" . $ulng->txt('survey_notification_tutor_link') . ": " . $link;
6188
6189            $mail_obj = new ilMail(ANONYMOUS_USER_ID);
6190            $mail_obj->appendInstallationSignature(true);
6191            $log->debug("send mail to user id: " . $user_id . ",login: " . ilObjUser::_lookupLogin($user_id));
6192            $mail_obj->enqueue(
6193                ilObjUser::_lookupLogin($user_id),
6194                "",
6195                "",
6196                $subject,
6197                $message,
6198                array($att)
6199            );
6200        }
6201
6202        $ilDB->manipulate("UPDATE svy_svy" .
6203            " SET tutor_res_cron = " . $ilDB->quote(1, "integer") .
6204            " WHERE survey_id = " . $ilDB->quote($this->getSurveyId(), "integer"));
6205
6206        return true;
6207    }
6208} // END class.ilObjSurvey
6209