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