1<?php
2/* Copyright (c) 1998-2009 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4/**
5 * Membership notification settings
6 *
7 * @author Jörg Lützenkirchen <luetzenkirchen@leifos.com>
8 * @ingroup ServicesMembership
9 */
10class ilMembershipNotifications
11{
12    protected $ref_id; // [int]
13    protected $mode; // [int]
14    protected $custom; // [array]
15    protected $participants; // [ilParticipants]
16
17    const VALUE_OFF = 0;
18    const VALUE_ON = 1;
19    const VALUE_BLOCKED = 2;
20
21    const MODE_SELF = 1;
22    const MODE_ALL = 2;
23    const MODE_ALL_BLOCKED = 3;
24    const MODE_CUSTOM = 4;
25
26    /**
27     * Constructor
28     *
29     * @param int $a_ref_id
30     * @return self
31     */
32    public function __construct($a_ref_id)
33    {
34        $this->ref_id = (int) $a_ref_id;
35        $this->custom = array();
36        $this->setMode(self::MODE_SELF);
37
38        if ($this->ref_id) {
39            $this->read();
40        }
41    }
42
43    /**
44     * Is feature active?
45     *
46     * @return bool
47     */
48    public static function isActive()
49    {
50        global $DIC;
51
52        $ilSetting = $DIC['ilSetting'];
53
54        if (!$ilSetting->get("block_activated_news") || !$ilSetting->get("crsgrp_ntf")) {
55            return false;
56        }
57        return true;
58    }
59
60    public static function isActiveForRefId(int $ref_id)
61    {
62        if (!self::isActive()) {
63            return false;
64        }
65        // see #31471, #30687, and ilNewsItem::getNewsForRefId
66        $obj_id = ilObject::_lookupObjId($ref_id);
67        if (!ilContainer::_lookupContainerSetting(
68                $obj_id,
69                'cont_use_news',
70                true
71            ) || (
72                !ilContainer::_lookupContainerSetting(
73                    $obj_id,
74                    'cont_show_news',
75                    true
76                ) && !ilContainer::_lookupContainerSetting(
77                    $obj_id,
78                    'news_timeline'
79                )
80            )) {
81            return false;
82        }
83        return true;
84    }
85
86    /**
87     * Read from DB
88     */
89    protected function read()
90    {
91        global $DIC;
92
93        $ilDB = $DIC['ilDB'];
94
95        $set = $ilDB->query("SELECT nmode mode" .
96            " FROM member_noti" .
97            " WHERE ref_id = " . $ilDB->quote($this->ref_id, "integer"));
98        if ($ilDB->numRows($set)) {
99            $row = $ilDB->fetchAssoc($set);
100            $this->setMode($row["mode"]);
101
102            if ($row["mode"] == self::MODE_CUSTOM) {
103                $set = $ilDB->query("SELECT *" .
104                    " FROM member_noti_user" .
105                    " WHERE ref_id = " . $ilDB->quote($this->ref_id, "integer"));
106                while ($row = $ilDB->fetchAssoc($set)) {
107                    $this->custom[$row["user_id"]] = $row["status"];
108                }
109            }
110        }
111    }
112
113
114    //
115    // MODE
116    //
117
118    /**
119     * Get mode
120     *
121     * @return int
122     */
123    public function getMode()
124    {
125        return $this->mode;
126    }
127
128    /**
129     * Set mode
130     *
131     * @param int $a_value
132     */
133    protected function setMode($a_value)
134    {
135        if ($this->isValidMode($a_value)) {
136            $this->mode = $a_value;
137        }
138    }
139
140    /**
141     * Is given mode valid?
142     *
143     * @param int $a_value
144     * @return bool
145     */
146    protected function isValidMode($a_value)
147    {
148        $valid = array(
149            self::MODE_SELF
150            ,self::MODE_ALL
151            ,self::MODE_ALL_BLOCKED
152            // ,self::MODE_CUSTOM currently used in forum
153        );
154        return in_array($a_value, $valid);
155    }
156
157    /**
158     * Switch mode for object
159     *
160     * @param int $a_new_mode
161     * @return bool
162     */
163    public function switchMode($a_new_mode)
164    {
165        global $DIC;
166
167        $ilDB = $DIC['ilDB'];
168
169        if (!$this->ref_id) {
170            return;
171        }
172
173        if ($this->mode &&
174            $this->mode != $a_new_mode &&
175            $this->isValidMode($a_new_mode)) {
176            $ilDB->manipulate("DELETE FROM member_noti" .
177                " WHERE ref_id = " . $ilDB->quote($this->ref_id, "integer"));
178
179            // no custom data
180            if ($a_new_mode != self::MODE_CUSTOM) {
181                $ilDB->manipulate("DELETE FROM member_noti_user" .
182                    " WHERE ref_id = " . $ilDB->quote($this->ref_id, "integer"));
183            }
184
185            // mode self is default
186            if ($a_new_mode != self::MODE_SELF) {
187                $ilDB->insert("member_noti", array(
188                    "ref_id" => array("integer", $this->ref_id),
189                    "nmode" => array("integer", $a_new_mode)
190                ));
191            }
192
193            // remove all user settings (all active is preset, optional opt out)
194            if ($a_new_mode == self::MODE_ALL) {
195                $ilDB->manipulate("DELETE FROM usr_pref" .
196                    " WHERE " . $ilDB->like("keyword", "text", "grpcrs_ntf_" . $this->ref_id));
197            }
198        }
199
200        $this->setMode($a_new_mode);
201    }
202
203
204    //
205    // ACTIVE USERS
206    //
207
208    /**
209     * Init participants for current object
210     *
211     * @return ilParticipants
212     */
213    protected function getParticipants()
214    {
215        global $DIC;
216
217        $tree = $DIC['tree'];
218
219        if ($this->participants === null) {
220            $this->participants = false;
221
222            $grp_ref_id = $tree->checkForParentType($this->ref_id, "grp");
223            if ($grp_ref_id) {
224                include_once "Modules/Group/classes/class.ilGroupParticipants.php";
225                $this->participants = ilGroupParticipants::_getInstanceByObjId(ilObject::_lookupObjId($grp_ref_id));
226            }
227
228            if (!$this->participants) {
229                $crs_ref_id = $tree->checkForParentType($this->ref_id, "crs");
230                if ($crs_ref_id) {
231                    include_once "Modules/Course/classes/class.ilCourseParticipants.php";
232                    $this->participants = ilCourseParticipants::_getInstanceByObjId(ilObject::_lookupObjId($crs_ref_id));
233                }
234            }
235        }
236
237        return $this->participants;
238    }
239
240    /**
241     * Get active notifications for current object
242     *
243     * @return array
244     */
245    public function getActiveUsers()
246    {
247        global $DIC;
248
249        $ilDB = $DIC->database();
250
251        $users = $all = array();
252
253        $part_obj = $this->getParticipants();
254        if ($part_obj) {
255            $all = $part_obj->getParticipants();
256        }
257        if (!sizeof($all)) {
258            return array();
259        }
260
261        switch ($this->getMode()) {
262            // users decide themselves
263            case self::MODE_SELF:
264                $set = $ilDB->query("SELECT usr_id" .
265                    " FROM usr_pref" .
266                    " WHERE keyword = " . $ilDB->quote("grpcrs_ntf_".$this->ref_id, "text") .
267                    " AND value = " . $ilDB->quote(self::VALUE_ON, "text"));
268                while ($row = $ilDB->fetchAssoc($set)) {
269                    $users[] = $row["usr_id"];
270                }
271                break;
272
273            // all members, mind opt-out
274            case self::MODE_ALL:
275                // users who did opt-out
276                $inactive = array();
277                $set = $ilDB->query("SELECT usr_id" .
278                    " FROM usr_pref" .
279                    " WHERE keyword = " . $ilDB->quote("grpcrs_ntf_".$this->ref_id, "text") .
280                    " AND value = " . $ilDB->quote(self::VALUE_OFF, "text"));
281                while ($row = $ilDB->fetchAssoc($set)) {
282                    $inactive[] = $row["usr_id"];
283                }
284                $users = array_diff($all, $inactive);
285                break;
286
287            // all members, no opt-out
288            case self::MODE_ALL_BLOCKED:
289                $users = $all;
290                break;
291
292            // custom settings
293            case self::MODE_CUSTOM:
294                foreach ($this->custom as $user_id => $status) {
295                    if ($status != self::VALUE_OFF) {
296                        $users[] = $user_id;
297                    }
298                }
299                break;
300        }
301
302        // only valid participants
303        return  array_intersect($all, $users);
304    }
305
306
307    //
308    // USER STATUS
309    //
310
311    /**
312     * Activate notification for user
313     *
314     * @param int $a_user_id
315     * @return bool
316     */
317    public function activateUser($a_user_id = null)
318    {
319        return $this->toggleUser(true, $a_user_id);
320    }
321
322    /**
323     * Deactivate notification for user
324     *
325     * @param int $a_user_id
326     * @return bool
327     */
328    public function deactivateUser($a_user_id = null)
329    {
330        return $this->toggleUser(false, $a_user_id);
331    }
332
333    /**
334     * Init user instance
335     *
336     * @param int $a_user_id
337     * @return ilUser
338     */
339    protected function getUser($a_user_id = null)
340    {
341        global $DIC;
342
343        $ilUser = $DIC['ilUser'];
344
345        if ($a_user_id === null ||
346            $a_user_id == $ilUser->getId()) {
347            $user = $ilUser;
348        } else {
349            $user = new ilUser($a_user_id);
350        }
351
352        if ($user->getId() &&
353            $user->getId() != ANONYMOUS_USER_ID) {
354            return $user;
355        }
356    }
357
358    /**
359     * Toggle user notification status
360     *
361     * @param bool $a_status
362     * @param int $a_user_id
363     * @return boolean
364     */
365    protected function toggleUser($a_status, $a_user_id = null)
366    {
367        global $DIC;
368
369        $ilDB = $DIC['ilDB'];
370
371        if (!self::isActive()) {
372            return;
373        }
374
375        switch ($this->getMode()) {
376            case self::MODE_ALL:
377            case self::MODE_SELF:
378                // current user!
379                $user = $this->getUser();
380                if ($user) {
381                    // blocked value not supported in user pref!
382                    $user->setPref("grpcrs_ntf_" . $this->ref_id, (int) (bool) $a_status);
383                    $user->writePrefs();
384                    return true;
385                }
386                break;
387
388            case self::MODE_CUSTOM:
389                $user = $this->getUser($a_user_id);
390                if ($user) {
391                    $user_id = $user->getId();
392
393                    // did status change at all?
394                    if (!array_key_exists($user_id, $this->custom) ||
395                        $this->custom[$user_id != $a_status]) {
396                        $this->custom[$user_id] = $a_status;
397
398                        $ilDB->replace(
399                            "member_noti_user",
400                            array(
401                                "ref_id" => array("integer", $this->ref_id),
402                                "user_id" => array("integer", $user_id),
403                            ),
404                            array(
405                                "status" => array("integer", $a_status)
406                            )
407                        );
408                    }
409                    return true;
410                }
411                break;
412
413            case self::MODE_ALL_BLOCKED:
414                // no individual settings
415                break;
416        }
417
418        return false;
419    }
420
421
422    //
423    // CURRENT USER
424    //
425
426    /**
427     * Get user notification status
428     *
429     * @return boolean
430     */
431    public function isCurrentUserActive()
432    {
433        global $DIC;
434
435        $ilUser = $DIC['ilUser'];
436
437        return in_array($ilUser->getId(), $this->getActiveUsers());
438    }
439
440    /**
441     * Can user change notification status?
442     *
443     * @return boolean
444     */
445    public function canCurrentUserEdit()
446    {
447        global $DIC;
448
449        $ilUser = $DIC['ilUser'];
450
451        $user_id = $ilUser->getId();
452        if ($user_id == ANONYMOUS_USER_ID) {
453            return false;
454        }
455
456        switch ($this->getMode()) {
457            case self::MODE_SELF:
458            case self::MODE_ALL:
459                return true;
460
461            case self::MODE_ALL_BLOCKED:
462                return false;
463
464            case self::MODE_CUSTOM:
465                return !(array_key_exists($user_id, $this->custom) &&
466                    $this->custom[$user_id] == self::VALUE_BLOCKED);
467        }
468    }
469
470
471    //
472    // CRON
473    //
474
475    /**
476     * Get active notifications for all objects
477     *
478     * @return array
479     */
480    public static function getActiveUsersforAllObjects()
481    {
482        global $DIC;
483
484        $ilDB = $DIC['ilDB'];
485        $tree = $DIC['tree'];
486
487        $log = ilLoggerFactory::getLogger("mmbr");
488
489
490        $res = array();
491
492        if (self::isActive()) {
493            $objects = array();
494
495            // user-preference data (MODE_SELF)
496            $log->debug("read usr_pref");
497            $set = $ilDB->query("SELECT DISTINCT(keyword) keyword" .
498                " FROM usr_pref" .
499                " WHERE " . $ilDB->like("keyword", "text", "grpcrs_ntf_%") .
500                " AND value = " . $ilDB->quote("1", "text"));
501            while ($row = $ilDB->fetchAssoc($set)) {
502                $ref_id = substr($row["keyword"], 11);
503                $objects[(int) $ref_id] = (int) $ref_id;
504            }
505
506            // all other modes
507            $log->debug("read member_noti");
508            $set = $ilDB->query("SELECT ref_id" .
509                " FROM member_noti");
510            while ($row = $ilDB->fetchAssoc($set)) {
511                $objects[(int) $row["ref_id"]] = (int) $row["ref_id"];
512            }
513
514            // this might be slow but it is to be used in CRON JOB ONLY!
515            foreach (array_unique($objects) as $ref_id) {
516                // :TODO: enough checking?
517                if (!$tree->isDeleted($ref_id)) {
518                    $log->debug("get active users");
519                    $noti = new self($ref_id);
520                    $active = $noti->getActiveUsers();
521                    if (sizeof($active)) {
522                        $res[$ref_id] = $active;
523                    }
524                }
525            }
526        }
527
528        return $res;
529    }
530
531
532    //
533    // (OBJECT SETTINGS) FORM
534    //
535
536    /**
537     * Add notification settings to form
538     *
539     * @param int $a_ref_id
540     * @param ilPropertyFormGUI $a_form
541     * @param ilPropertyFormGUI $a_input
542     */
543    public static function addToSettingsForm($a_ref_id, ilPropertyFormGUI $a_form = null, ilFormPropertyGUI $a_input = null)
544    {
545        global $DIC;
546
547        $lng = $DIC['lng'];
548
549        if (self::isActive() &&
550            $a_ref_id) {
551            $lng->loadLanguageModule("membership");
552            $noti = new self($a_ref_id);
553
554            $force_noti = new ilRadioGroupInputGUI($lng->txt("mem_force_notification"), "force_noti");
555            $force_noti->setRequired(true);
556            if ($a_form) {
557                $a_form->addItem($force_noti);
558            } else {
559                $a_input->addSubItem($force_noti);
560            }
561
562            if ($noti->isValidMode(self::MODE_SELF)) {
563                $option = new ilRadioOption($lng->txt("mem_force_notification_mode_self"), self::MODE_SELF);
564                $force_noti->addOption($option);
565            }
566            if ($noti->isValidMode(self::MODE_ALL_BLOCKED)) {
567                $option = new ilRadioOption($lng->txt("mem_force_notification_mode_blocked"), self::MODE_ALL_BLOCKED);
568                $force_noti->addOption($option);
569
570                if ($noti->isValidMode(self::MODE_ALL)) {
571                    $changeable = new ilCheckboxInputGUI($lng->txt("mem_force_notification_mode_all_sub_blocked"), "force_noti_allblk");
572                    $option->addSubItem($changeable);
573                }
574            } elseif ($noti->isValidMode(self::MODE_ALL)) {
575                $option = new ilRadioOption($lng->txt("mem_force_notification_mode_all"), self::MODE_ALL);
576                $force_noti->addOption($option);
577            }
578            /* not supported in GUI
579            if($noti->isValidMode(self::MODE_CUSTOM))
580            {
581                $option = new ilRadioOption($lng->txt("mem_force_notification_mode_custom"), self::MODE_CUSTOM);
582                $option->setInfo($lng->txt("mem_force_notification_mode_custom_info"));
583                $force_noti->addOption($option);
584            }
585            */
586
587            // set current mode
588            $current_mode = $noti->getMode();
589            $has_changeable_cb = ($noti->isValidMode(self::MODE_ALL_BLOCKED) &&
590                $noti->isValidMode(self::MODE_ALL));
591            if (!$has_changeable_cb) {
592                $force_noti->setValue($current_mode);
593            } else {
594                switch ($current_mode) {
595                    case self::MODE_SELF:
596                        $force_noti->setValue($current_mode);
597                        $changeable->setChecked(true); // checked as "default" on selection of parent
598                        break;
599
600                    case self::MODE_ALL_BLOCKED:
601                        $force_noti->setValue($current_mode);
602                        break;
603
604                    case self::MODE_ALL:
605                        $force_noti->setValue(self::MODE_ALL_BLOCKED);
606                        $changeable->setChecked(true);
607                        break;
608                }
609            }
610        }
611    }
612
613    /**
614     * Import notification settings from form
615     *
616     * @param int $a_ref_id
617     * @param ilPropertyFormGUI $a_form
618     */
619    public static function importFromForm($a_ref_id, ilPropertyFormGUI $a_form = null)
620    {
621        if (self::isActive() &&
622            $a_ref_id) {
623            $noti = new self($a_ref_id);
624            $has_changeable_cb = ($noti->isValidMode(self::MODE_ALL_BLOCKED) &&
625                $noti->isValidMode(self::MODE_ALL));
626            $changeable = null;
627            if (!$a_form) {
628                $mode = (int) $_POST["force_noti"];
629                if ($has_changeable_cb) {
630                    $changeable = (int) $_POST["force_noti_allblk"];
631                }
632            } else {
633                $mode = $a_form->getInput("force_noti");
634                if ($has_changeable_cb) {
635                    $changeable = $a_form->getInput("force_noti_allblk");
636                }
637            }
638            // checkbox (all) is subitem of all_blocked
639            if ($changeable &&
640                $mode == self::MODE_ALL_BLOCKED) {
641                $mode = self::MODE_ALL;
642            }
643            $noti->switchMode($mode);
644        }
645    }
646
647    /**
648     * Clone notification object settings
649     *
650     * @param $new_ref_id
651     */
652    public function cloneSettings($new_ref_id)
653    {
654        global $ilDB;
655
656        $set = $ilDB->queryF(
657            "SELECT * FROM member_noti " .
658            " WHERE ref_id = %s ",
659            array("integer"),
660            array($this->ref_id)
661        );
662        while ($rec = $ilDB->fetchAssoc($set)) {
663            $ilDB->insert("member_noti", array(
664                "ref_id" => array("integer", $new_ref_id),
665                "nmode" => array("integer", $rec["nmode"])
666            ));
667        }
668    }
669}
670