ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.ilObjPoll.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
28 class ilObjPoll extends ilObject2
29 {
30  protected \ILIAS\Notes\Service $notes;
31  protected int $access_type = 0;
32  protected int $access_begin = 0;
33  protected int $access_end = 0;
34  protected bool $access_visibility = false;
35  protected string $question = "";
36  protected string $image = "";
37  protected int $view_results = 0;
38  protected bool $period = false;
39  protected int $period_begin = 0;
40  protected int $period_end = 0;
41 
42  protected int $max_number_answers = 0;
43  protected bool $result_sort_by_votes = false;
44  protected bool $mode_non_anonymous = false;
45  protected bool $show_comments = false;
46  protected int $show_results_as = 1;
47 
48  public const VIEW_RESULTS_ALWAYS = 1;
49  public const VIEW_RESULTS_NEVER = 2;
50  public const VIEW_RESULTS_AFTER_VOTE = 3;
51  public const VIEW_RESULTS_AFTER_PERIOD = 4;
52 
53  public const SHOW_RESULTS_AS_BARCHART = 1;
54  public const SHOW_RESULTS_AS_PIECHART = 2;
55  private \ILIAS\Filesystem\Util\Convert\LegacyImages $image_converter;
56 
57  public function __construct(int $a_id = 0, bool $a_reference = true)
58  {
59  global $DIC;
60 
61  $this->db = $DIC->database();
62  $this->image_converter = $DIC->fileConverters()->legacyImages();
63  // default
64  $this->setViewResults(self::VIEW_RESULTS_AFTER_VOTE);
66  $this->setVotingPeriod(false);
67  $this->notes = $DIC->notes();
68 
69  parent::__construct($a_id, $a_reference);
70  }
71 
72  protected function initType(): void
73  {
74  $this->type = "poll";
75  }
76 
77  public function setAccessType(int $a_value): void
78  {
79  $this->access_type = $a_value;
80  }
81 
82  public function getAccessType(): int
83  {
84  return $this->access_type;
85  }
86 
87  public function setAccessBegin(int $a_value): void
88  {
89  $this->access_begin = $a_value;
90  }
91 
92  public function getAccessBegin(): int
93  {
94  return $this->access_begin;
95  }
96 
97  public function setAccessEnd(int $a_value): void
98  {
99  $this->access_end = $a_value;
100  }
101 
102  public function getAccessEnd(): int
103  {
104  return $this->access_end;
105  }
106 
107  public function setAccessVisibility(bool $a_value): void
108  {
109  $this->access_visibility = $a_value;
110  }
111 
112  public function getAccessVisibility(): bool
113  {
115  }
116 
117  public function setQuestion(string $a_value): void
118  {
119  $this->question = $a_value;
120  }
121 
122  public function getQuestion(): string
123  {
124  return $this->question;
125  }
126 
127  public function setImage(string $a_value): void
128  {
129  $this->image = $a_value;
130  }
131 
132  public function getImage(): string
133  {
134  return $this->image;
135  }
136 
137  public function setViewResults(int $a_value): void
138  {
139  $this->view_results = $a_value;
140  }
141 
142  public function getViewResults(): int
143  {
144  return $this->view_results;
145  }
146 
147  public function setVotingPeriod(bool $a_value): void
148  {
149  $this->period = $a_value;
150  }
151 
152  public function getVotingPeriod(): bool
153  {
154  return $this->period;
155  }
156 
157  public function setVotingPeriodBegin(int $a_value): void
158  {
159  $this->period_begin = $a_value;
160  }
161 
162  public function getVotingPeriodBegin(): int
163  {
164  return $this->period_begin;
165  }
166 
167  public function setVotingPeriodEnd(int $a_value): void
168  {
169  $this->period_end = $a_value;
170  }
171 
172  public function getVotingPeriodEnd(): int
173  {
174  return $this->period_end;
175  }
176 
177  public function setMaxNumberOfAnswers(int $a_value): void
178  {
179  $this->max_number_answers = $a_value;
180  }
181 
182  public function getMaxNumberOfAnswers(): int
183  {
185  }
186 
187  public function setSortResultByVotes(bool $a_value): void
188  {
189  $this->result_sort_by_votes = $a_value;
190  }
191 
192  public function getSortResultByVotes(): bool
193  {
195  }
196 
197  public function setNonAnonymous(bool $a_value): void
198  {
199  $this->mode_non_anonymous = $a_value;
200  }
201 
202  public function getNonAnonymous(): bool
203  {
205  }
206 
207  public function setShowComments(bool $a_value): void
208  {
209  $this->show_comments = $a_value;
210  }
211 
212  public function getShowComments(): bool
213  {
214  return $this->show_comments;
215  }
216 
217  public function setShowResultsAs(int $a_value): void
218  {
219  $this->show_results_as = $a_value;
220  }
221 
222  public function getShowResultsAs(): int
223  {
224  return $this->show_results_as;
225  }
226 
227  protected function doRead(): void
228  {
229  $ilDB = $this->db;
230 
231  $set = $ilDB->query("SELECT * FROM il_poll" .
232  " WHERE id = " . $ilDB->quote($this->getId(), "integer"));
233  $row = $ilDB->fetchAssoc($set);
234  $this->setQuestion((string) ($row["question"] ?? ''));
235  $this->setImage((string) ($row["image"] ?? ''));
236  $this->setViewResults((int) ($row["view_results"] ?? self::VIEW_RESULTS_AFTER_VOTE));
237  $this->setVotingPeriod((bool) ($row["period"] ?? 0));
238  $this->setVotingPeriodBegin((int) ($row["period_begin"] ?? 0));
239  $this->setVotingPeriodEnd((int) ($row["period_end"] ?? 0));
240  $this->setMaxNumberOfAnswers((int) ($row["max_answers"] ?? 0));
241  $this->setSortResultByVotes((bool) ($row["result_sort"] ?? 0));
242  $this->setNonAnonymous((bool) ($row["non_anon"] ?? 0));
243  $this->setShowResultsAs((int) ($row["show_results_as"] ?? self::SHOW_RESULTS_AS_BARCHART));
244 
245  // #14661
246  $this->setShowComments($this->notes->domain()->commentsActive($this->getId()));
247 
248  if ($this->ref_id) {
249  $activation = ilObjectActivation::getItem($this->ref_id);
250  $this->setAccessType((int) ($activation["timing_type"] ?? ilObjectActivation::TIMINGS_DEACTIVATED));
252  // default entry values should not be loaded if not activated
253  $this->setAccessBegin((int) ($activation["timing_start"] ?? time()));
254  $this->setAccessEnd((int) ($activation["timing_end"] ?? time()));
255  $this->setAccessVisibility((bool) ($activation["visible"] ?? false));
256  }
257  }
258  }
259 
260  protected function propertiesToDB(): array
261  {
262  return array(
263  "question" => array("text", $this->getQuestion()),
264  "image" => array("text", $this->getImage()),
265  "view_results" => array("integer", $this->getViewResults()),
266  "period" => array("integer", $this->getVotingPeriod()),
267  "period_begin" => array("integer", $this->getVotingPeriodBegin()),
268  "period_end" => array("integer", $this->getVotingPeriodEnd()),
269  "max_answers" => array("integer", $this->getMaxNumberOfAnswers()),
270  "result_sort" => array("integer", $this->getSortResultByVotes()),
271  "non_anon" => array("integer", $this->getNonAnonymous()),
272  "show_results_as" => array("integer", $this->getShowResultsAs()),
273  );
274  }
275 
276  protected function doCreate(bool $clone_mode = false): void
277  {
278  $ilDB = $this->db;
279 
280  if ($this->getId()) {
281  $fields = $this->propertiesToDB();
282  $fields["id"] = array("integer", $this->getId());
283 
284  $ilDB->insert("il_poll", $fields);
285 
286 
287  // object activation default entry will be created on demand
288 
289 
290  // block handling
291  $block = new ilPollBlock();
292  $block->setType("poll");
293  $block->setContextObjId($this->getId());
294  $block->setContextObjType("poll");
295  $block->create();
296  }
297  }
298 
299  protected function doUpdate(): void
300  {
301  $ilDB = $this->db;
302 
303  if ($this->getId()) {
304  $fields = $this->propertiesToDB();
305 
306  $ilDB->update(
307  "il_poll",
308  $fields,
309  array("id" => array("integer", $this->getId()))
310  );
311 
312  // #14661
313  $this->notes->domain()->activateComments($this->getId(), $this->getShowComments());
314 
315  if ($this->getRefId()) {
316  $activation = new ilObjectActivation();
317  $activation->setTimingType($this->getAccessType());
318  $activation->setTimingStart($this->getAccessBegin());
319  $activation->setTimingEnd($this->getAccessEnd());
320  $activation->toggleVisible($this->getAccessVisibility());
321  $activation->update($this->ref_id);
322  }
323  }
324  }
325 
326  protected function doDelete(): void
327  {
328  $ilDB = $this->db;
329 
330  if ($this->getId()) {
331  $this->deleteImage();
332  $this->deleteAllAnswers();
333 
334  if ($this->ref_id) {
336  }
337 
338  $ilDB->manipulate("DELETE FROM il_poll" .
339  " WHERE id = " . $ilDB->quote($this->id, "integer"));
340  }
341  }
342 
343  protected function doCloneObject(ilObject2 $new_obj, int $a_target_id, ?int $a_copy_id = 0): void
344  {
345  assert($new_obj instanceof ilObjPoll);
346 
347  // question/image
348  $new_obj->setQuestion($this->getQuestion());
349  $image = $this->getImageFullPath();
350  if ($image) {
351  $image = array("tmp_name" => $image,
352  "name" => $this->getImage());
353  $new_obj->uploadImage($image, true);
354  }
355 
356  //copy online status if object is not the root copy object
357  $cp_options = ilCopyWizardOptions::_getInstance($a_copy_id);
358 
359  if ($cp_options->isRootNode($this->getRefId())) {
360  $new_obj->setOfflineStatus(true);
361  }
362 
363  $view_results = $this->getViewResults();
364  if ($view_results === ilObjPoll::VIEW_RESULTS_AFTER_PERIOD) {
365  // default view results setting to always, since
366  // voting period is not copied.
367  $view_results = ilObjPoll::VIEW_RESULTS_ALWAYS;
368  }
369  $new_obj->setViewResults($view_results);
370  $new_obj->setShowComments($this->getShowComments());
371  $new_obj->setShowResultsAs($this->getShowResultsAs());
372  $new_obj->setMaxNumberOfAnswers($this->getMaxNumberOfAnswers());
373  $new_obj->setSortResultByVotes($this->getSortResultByVotes());
374  $new_obj->setNonAnonymous($this->getNonAnonymous());
375  $new_obj->update();
376 
377  // answers
378  $answers = $this->getAnswers();
379  if ($answers) {
380  foreach ($answers as $item) {
381  $new_obj->saveAnswer($item["answer"]);
382  }
383  }
384  }
385 
386 
387  //
388  // image
389  //
390 
391  public function getImageFullPath(bool $a_as_thumb = false): ?string
392  {
393  $img = $this->getImage();
394  if ($img) {
395  $path = self::initStorage($this->id);
396  if (!$a_as_thumb) {
397  return $path . $img;
398  } else {
399  return $path . "thb_" . $img;
400  }
401  }
402 
403  return null;
404  }
405 
406  public function deleteImage(): void
407  {
408  if ($this->id) {
409  $storage = new ilFSStoragePoll($this->id);
410  $storage->delete();
411 
412  $this->setImage("");
413  }
414  }
415 
416  public static function initStorage(int $a_id, ?string $a_subdir = null): string
417  {
418  $storage = new ilFSStoragePoll($a_id);
419  $storage->create();
420 
421  $path = $storage->getAbsolutePath() . "/";
422 
423  if ($a_subdir) {
424  $path .= $a_subdir . "/";
425 
426  if (!is_dir($path)) {
427  mkdir($path);
428  }
429  }
430 
431  return $path;
432  }
433 
434  public function uploadImage(array $a_upload, bool $a_clone = false): bool
435  {
436  if (!$this->id) {
437  return false;
438  }
439 
440  $this->deleteImage();
441 
442  // #10074
443  $name = (string) ($a_upload['name'] ?? '');
444  $tmp_name = (string) ($a_upload['tmp_name'] ?? '');
445  $clean_name = preg_replace("/[^a-zA-Z0-9\_\.\-]/", "", $name);
446 
447  $path = self::initStorage($this->id);
448  $original = "org_" . $this->id . "_" . $clean_name;
449  $thumb = "thb_" . $this->id . "_" . $clean_name;
450  $processed = $this->id . "_" . $clean_name;
451 
452  $success = false;
453  if (!$a_clone) {
454  $success = ilFileUtils::moveUploadedFile($tmp_name, $original, $path . $original);
455  } else {
456  $success = copy($tmp_name, $path . $original);
457  }
458  if ($success) {
459  chmod($path . $original, 0770);
460 
461  // take quality 100 to avoid jpeg artefacts when uploading jpeg files
462  $original_file = $path . $original;
463  $thumb_file = $path . $thumb;
464  $processed_file = $path . $processed;
465 
466  $this->image_converter->croppedSquare(
467  $original_file,
468  $thumb_file,
469  100,
470  ImageOutputOptions::FORMAT_PNG
471  );
472 
473  $this->image_converter->croppedSquare(
474  $original_file,
475  $processed_file,
476  300,
477  ImageOutputOptions::FORMAT_PNG
478  );
479 
480  $this->setImage($processed);
481  return true;
482  }
483  return false;
484  }
485 
486  public static function getImageSize(): string
487  {
488  // :TODO:
489  return "600x600";
490  }
491 
492 
493  //
494  // Answer
495  //
496 
497  public function getAnswers(): array
498  {
499  $ilDB = $this->db;
500 
501  $res = [];
502 
503  $sql = "SELECT * FROM il_poll_answer" .
504  " WHERE poll_id = " . $ilDB->quote($this->getId(), "integer") .
505  " ORDER BY pos ASC";
506  $set = $ilDB->query($sql);
507  while ($row = $ilDB->fetchAssoc($set)) {
508  $res[] = $row;
509  }
510  return $res;
511  }
512 
513  public function getAnswer(int $a_id): array
514  {
515  $ilDB = $this->db;
516 
517  $sql = "SELECT * FROM il_poll_answer" .
518  " WHERE id = " . $ilDB->quote($a_id, "integer");
519  $set = $ilDB->query($sql);
520  return (array) $ilDB->fetchAssoc($set);
521  }
522 
523  public function saveAnswer(string $a_text, ?int $a_pos = null): ?int
524  {
525  $ilDB = $this->db;
526 
527  if (!trim($a_text)) {
528  return null;
529  }
530 
531  $id = $ilDB->nextId("il_poll_answer");
532 
533  if (!$a_pos) {
534  // append
535  $sql = "SELECT max(pos) pos" .
536  " FROM il_poll_answer" .
537  " WHERE poll_id = " . $ilDB->quote($this->getId(), "integer");
538  $set = $ilDB->query($sql);
539  $a_pos = $ilDB->fetchAssoc($set);
540  $a_pos = (int) ($a_pos["pos"] ?? 0) + 10;
541  }
542 
543  $fields = array(
544  "id" => array("integer", $id),
545  "poll_id" => array("integer", $this->getId()),
546  "answer" => array("text", trim($a_text)),
547  "pos" => array("integer", $a_pos)
548  );
549  $ilDB->insert("il_poll_answer", $fields);
550 
551  return $id;
552  }
553 
554  public function updateAnswer(int $a_id, string $a_text): void
555  {
556  $ilDB = $this->db;
557 
558  $ilDB->update(
559  "il_poll_answer",
560  array("answer" => array("text", $a_text)),
561  array("id" => array("integer", $a_id))
562  );
563  }
564 
565  public function rebuildAnswerPositions(): void
566  {
567  $answers = $this->getAnswers();
568 
569  $pos = [];
570  foreach ($answers as $item) {
571  $id = (int) ($item['id'] ?? 0);
572  $pos[$id] = (int) ($item["pos"] ?? 10);
573  }
574 
575  $this->updateAnswerPositions($pos);
576  }
577 
578  public function updateAnswerPositions(array $a_pos): void
579  {
580  $ilDB = $this->db;
581 
582  asort($a_pos);
583 
584  $pos = 0;
585  foreach (array_keys($a_pos) as $id) {
586  $pos += 10;
587 
588  $ilDB->update(
589  "il_poll_answer",
590  array("pos" => array("integer", $pos)),
591  array("id" => array("integer", $id))
592  );
593  }
594  }
595 
596  public function deleteAnswer(int $a_id): void
597  {
598  $ilDB = $this->db;
599 
600  if ($a_id) {
601  $ilDB->manipulate("DELETE FROM il_poll_vote" .
602  " WHERE answer_id = " . $ilDB->quote($this->getId(), "integer"));
603 
604  $ilDB->manipulate("DELETE FROM il_poll_answer" .
605  " WHERE id = " . $ilDB->quote($a_id, "integer"));
606  }
607  }
608 
609  protected function deleteAllAnswers(): void
610  {
611  $ilDB = $this->db;
612 
613  if ($this->getId()) {
614  $this->deleteAllVotes();
615 
616  $ilDB->manipulate("DELETE FROM il_poll_answer" .
617  " WHERE poll_id = " . $ilDB->quote($this->getId(), "integer"));
618  }
619  }
620 
621  public function deleteAllVotes(): void
622  {
623  $ilDB = $this->db;
624 
625  if ($this->getId()) {
626  $ilDB->manipulate("DELETE FROM il_poll_vote" .
627  " WHERE poll_id = " . $ilDB->quote($this->getId(), "integer"));
628  }
629  }
630 
631  public function saveAnswers(array $a_answers): int
632  {
633  $existing = $this->getAnswers();
634 
635  $ids = [];
636  $pos = 0;
637  $id = null;
638  foreach ($a_answers as $answer) {
639  if (trim($answer)) {
640  // existing answer?
641  $found = false;
642  foreach ($existing as $idx => $item) {
643  if (trim($answer) === (string) ($item["answer"] ?? '')) {
644  $found = true;
645  unset($existing[$idx]);
646 
647  $id = (int) ($item["id"] ?? 0);
648  }
649  }
650 
651  // create new answer
652  if (!$found) {
653  $id = $this->saveAnswer($answer);
654  }
655 
656  // add existing answer id to order
657  if (isset($id) && is_int($id)) {
658  $ids[$id] = ++$pos;
659  }
660  }
661  }
662 
663  // remove obsolete answers
664  if (count($existing)) {
665  foreach ($existing as $item) {
666  if (isset($item["id"])) {
667  $this->deleteAnswer((int) $item["id"]);
668  }
669  }
670  }
671 
672  // save current order
673  if (count($ids)) {
674  $this->updateAnswerPositions($ids);
675  }
676 
677  return count($ids);
678  }
679 
680 
681  //
682  // votes
683  //
684 
685  public function saveVote(int $a_user_id, array $a_answers): void
686  {
687  if ($this->hasUserVoted($a_user_id)) {
688  return;
689  }
690 
691  foreach ($a_answers as $answer_id) {
692  $fields = array("user_id" => array("integer", $a_user_id),
693  "poll_id" => array("integer", $this->getId()),
694  "answer_id" => array("integer", $answer_id));
695  $this->db->insert("il_poll_vote", $fields);
696  }
697  }
698 
699  public function hasUserVoted(int $a_user_id): bool
700  {
701  $sql = "SELECT user_id" .
702  " FROM il_poll_vote" .
703  " WHERE poll_id = " . $this->db->quote($this->getId(), "integer") .
704  " AND user_id = " . $this->db->quote($a_user_id, "integer");
705  $this->db->setLimit(1, 0);
706  $set = $this->db->query($sql);
707  return (bool) $this->db->numRows($set);
708  }
709 
710  public function countVotes(): int
711  {
712  $sql = "SELECT COUNT(DISTINCT(user_id)) cnt" .
713  " FROM il_poll_vote" .
714  " WHERE poll_id = " . $this->db->quote($this->getId(), "integer");
715  $set = $this->db->query($sql);
716  $row = $this->db->fetchAssoc($set);
717  return (int) $row["cnt"];
718  }
719 
720  public function getVotePercentages(): array
721  {
722  $res = [];
723  $cnt = 0;
724 
725  $sql = "SELECT answer_id, count(*) cnt" .
726  " FROM il_poll_vote" .
727  " WHERE poll_id = " . $this->db->quote($this->getId(), "integer") .
728  " GROUP BY answer_id";
729  $set = $this->db->query($sql);
730  while ($row = $this->db->fetchAssoc($set)) {
731  $cnt += (int) $row["cnt"];
732  $res[(int) $row["answer_id"]] = array("abs" => (int) $row["cnt"], "perc" => 0);
733  }
734 
735  foreach ($res as $id => $item) {
736  $abs = (int) ($item['abs'] ?? 0);
737  $id = (int) ($id ?? 0);
738  if ($cnt === 0) {
739  $res[$id]["perc"] = 0;
740  } else {
741  $res[$id]["perc"] = $abs / $cnt * 100;
742  }
743  }
744 
745  return array("perc" => $res, "total" => $this->countVotes());
746  }
747 
748  public function getVotesByUsers(): array
749  {
750  $ilDB = $this->db;
751 
752  $res = [];
753 
754  $sql = "SELECT answer_id, user_id, firstname, lastname, login" .
755  " FROM il_poll_vote" .
756  " JOIN usr_data ON (usr_data.usr_id = il_poll_vote.user_id)" .
757  " WHERE poll_id = " . $ilDB->quote($this->getId(), "integer");
758  $set = $ilDB->query($sql);
759  while ($row = $ilDB->fetchAssoc($set)) {
760  $user_id = (int) ($row["user_id"] ?? 0);
761  if (!isset($res[$user_id])) {
762  $res[$user_id] = $row;
763  }
764  $res[$user_id]["answers"][] = (int) ($row["answer_id"] ?? 0);
765  }
766 
767  return $res;
768  }
769 }
static deleteAllEntries(int $ref_id)
Delete all db entries for ref id.
setAccessBegin(int $a_value)
bool $show_comments
$res
Definition: ltiservices.php:69
const VIEW_RESULTS_AFTER_PERIOD
const VIEW_RESULTS_AFTER_VOTE
const VIEW_RESULTS_NEVER
string $question
hasUserVoted(int $a_user_id)
setSortResultByVotes(bool $a_value)
bool $result_sort_by_votes
setImage(string $a_value)
const VIEW_RESULTS_ALWAYS
setAccessType(int $a_value)
const SHOW_RESULTS_AS_PIECHART
setAccessEnd(int $a_value)
setQuestion(string $a_value)
updateAnswer(int $a_id, string $a_text)
bool $mode_non_anonymous
const SHOW_RESULTS_AS_BARCHART
$path
Definition: ltiservices.php:32
setShowComments(bool $a_value)
getAnswer(int $a_id)
global $DIC
Definition: feed.php:28
ILIAS Filesystem Util Convert LegacyImages $image_converter
setVotingPeriod(bool $a_value)
setMaxNumberOfAnswers(int $a_value)
__construct(VocabulariesInterface $vocabularies)
setShowResultsAs(int $a_value)
setNonAnonymous(bool $a_value)
setViewResults(int $a_value)
static getImageSize()
ilDBInterface $db
setVotingPeriodBegin(int $a_value)
updateAnswerPositions(array $a_pos)
static moveUploadedFile(string $a_file, string $a_name, string $a_target, bool $a_raise_errors=true, string $a_mode="move_uploaded")
move uploaded file
bool $access_visibility
__construct(int $a_id=0, bool $a_reference=true)
uploadImage(array $a_upload, bool $a_clone=false)
setOfflineStatus(bool $status)
Class ilObjPoll.
saveAnswers(array $a_answers)
setAccessVisibility(bool $a_value)
static getItem(int $ref_id)
int $show_results_as
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
doCreate(bool $clone_mode=false)
static initStorage(int $a_id, ?string $a_subdir=null)
int $max_number_answers
doCloneObject(ilObject2 $new_obj, int $a_target_id, ?int $a_copy_id=0)
deleteAnswer(int $a_id)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
ILIAS Notes Service $notes
saveAnswer(string $a_text, ?int $a_pos=null)
static _getInstance(int $a_copy_id)
Class ilObjectActivation.
saveVote(int $a_user_id, array $a_answers)
getImageFullPath(bool $a_as_thumb=false)
setVotingPeriodEnd(int $a_value)