1<?php
2
3declare(strict_types=1);
4
5use ILIAS\KioskMode\ControlBuilder;
6use ILIAS\KioskMode\LocatorBuilder;
7use ILIAS\KioskMode\TOCBuilder;
8use ILIAS\KioskMode\URLBuilder;
9
10use ILIAS\UI\Factory;
11
12/**
13 * Class LSControlBuilder
14 */
15class LSControlBuilder implements ControlBuilder
16{
17    const CMD_START_OBJECT = 'start_legacy_obj';
18    const CMD_CHECK_CURRENT_ITEM_LP = 'ccilp';
19    const PARAM_LP_CURRENT_ITEM_OBJID = 'ccilpobjid';
20
21    /**
22     * @var Component|null
23     */
24    protected $exit_control;
25
26    /**
27     * @var Component|null
28     */
29    protected $previous_control;
30
31    /**
32     * @var Component|null
33     */
34    protected $next_control;
35
36    /**
37     * @var Component|null
38     */
39    protected $done_control;
40
41    /**
42     * @var Component[]
43     */
44    protected $controls = [];
45
46    /**
47     * @var Component[]
48     */
49    protected $toggles = [];
50
51    /**
52     * @var Component[]
53     */
54    protected $mode_controls = [];
55
56    /**
57     * @var TOCBuilder|null
58     */
59    protected $toc;
60
61    /**
62     * @var LocatorBuilder|null
63     */
64    protected $loc;
65
66    /**
67     * @var Factory
68     */
69    protected $ui_factory;
70
71    /**
72     * @var URLBuilder
73     */
74    protected $url_builder;
75
76    /**
77     * @var Component|null
78     */
79    protected $start;
80
81    /**
82     * @var string | null
83     */
84    protected $additional_js;
85
86    /**
87     * @var LSGlobalSettings
88     */
89    protected $global_settings;
90
91    public function __construct(
92        Factory $ui_factory,
93        LSURLBuilder $url_builder,
94        ilLanguage $language,
95        LSGlobalSettings $global_settings
96    ) {
97        $this->ui_factory = $ui_factory;
98        $this->url_builder = $url_builder;
99        $this->lng = $language;
100        $this->global_settings = $global_settings;
101    }
102
103    public function getExitControl()
104    {
105        return $this->exit_control;
106    }
107
108    public function getPreviousControl()
109    {
110        return $this->previous_control;
111    }
112
113    public function getNextControl()
114    {
115        return $this->next_control;
116    }
117
118    public function getDoneControl()
119    {
120        return $this->done_control;
121    }
122
123    public function getToggles()
124    {
125        return $this->toggles;
126    }
127
128    public function getModeControls()
129    {
130        return $this->mode_controls;
131    }
132
133    public function getControls() : array
134    {
135        return $this->controls;
136    }
137
138    public function getLocator()
139    {
140        return $this->loc;
141    }
142
143    public function getToc()
144    {
145        return $this->toc;
146    }
147
148    /**
149     * @inheritdoc
150     */
151    public function exit(string $command) : ControlBuilder
152    {
153        if ($this->exit_control) {
154            throw new \LogicException("Only one exit-control per view...", 1);
155        }
156        $cmd = $this->url_builder->getHref($command);
157
158        $label = 'lso_player_suspend';
159        if ($command === ilLSPlayer::LSO_CMD_FINISH) {
160            $label = 'lso_player_finish';
161        }
162
163        $exit_button = $this->ui_factory->button()->shy(
164            $this->lng->txt($label),
165            $cmd
166        );
167
168        $this->exit_control = $exit_button;
169        return $this;
170    }
171
172    /**
173     * @inheritdoc
174     */
175    public function next(string $command, int $parameter = null) : ControlBuilder
176    {
177        if ($this->next_control) {
178            throw new \LogicException("Only one next-control per view...", 1);
179        }
180        $label = $this->lng->txt('lso_player_next');
181        $cmd = $this->url_builder->getHref($command, $parameter);
182        $btn = $this->ui_factory->button()->standard($label, $cmd);
183        if ($command === '') {
184            $btn = $btn->withUnavailableAction();
185        }
186        $this->next_control = $btn;
187        return $this;
188    }
189
190    /**
191     * @inheritdoc
192     */
193    public function previous(string $command, int $parameter = null) : ControlBuilder
194    {
195        if ($this->previous_control) {
196            throw new \LogicException("Only one previous-control per view...", 1);
197        }
198        $label = $this->lng->txt('lso_player_previous');
199        $cmd = $this->url_builder->getHref($command, $parameter);
200        $btn = $this->ui_factory->button()->standard($label, $cmd);
201        if ($command === '') {
202            $btn = $btn->withUnavailableAction();
203        }
204        $this->previous_control = $btn;
205        return $this;
206    }
207
208    /**
209     * @inheritdoc
210     */
211    public function done(string $command, int $parameter = null) : ControlBuilder
212    {
213        if ($this->done_control) {
214            throw new \LogicException("Only one done-control per view...", 1);
215        }
216        $label = $this->lng->txt('lso_player_done');
217        $cmd = $this->url_builder->getHref($command, $parameter);
218        $btn = $this->ui_factory->button()->primary($label, $cmd);
219        $this->done_control = $btn;
220        return $this;
221    }
222
223    /**
224     * @inheritdoc
225     */
226    public function generic(string $label, string $command, int $parameter = null) : ControlBuilder
227    {
228        $cmd = $this->url_builder->getHref($command, $parameter);
229        $this->controls[] = $this->ui_factory->button()->standard($label, $cmd);
230        return $this;
231    }
232
233    /**
234     * A toggle can be used to switch some behaviour in the view on or of.
235     */
236    public function toggle(string $label, string $on_command, string $off_command) : ControlBuilder
237    {
238        throw new \Exception("NYI: Toggles", 1);
239
240        $cmd_on = $this->url_builder->getHref($on_command, 0);
241        $cmd_off = $this->url_builder->getHref($off_command, 0);
242        //build toggle and add to $this->toggles
243        //return $this;
244    }
245
246    /**
247     * @inheritdoc
248     */
249    public function mode(string $command, array $labels) : ControlBuilder
250    {
251        $actions = [];
252        foreach ($labels as $parameter => $label) {
253            $actions[$label] = $this->url_builder->getHref($command, $parameter);
254        }
255        $this->mode_controls[] = $this->ui_factory->viewControl()->mode($actions, '');
256        return $this;
257    }
258
259    /**
260     * @inheritdoc
261     */
262    public function locator(string $command) : LocatorBuilder
263    {
264        if ($this->loc) {
265            throw new \LogicException("Only one locator per view...", 1);
266        }
267        $this->loc = new LSLocatorBuilder($command, $this);
268        return $this->loc;
269    }
270
271    /**
272     * @inheritdoc
273     */
274    public function tableOfContent(
275        string $label,
276        string $command,
277        int $parameter = null,
278        $state = null
279    ) : TOCBuilder {
280        if ($this->toc) {
281            throw new \LogicException("Only one ToC per view...", 1);
282        }
283        $this->toc = new LSTOCBuilder($this, $command, $label, $parameter, $state);
284        return $this->toc;
285    }
286
287    /**
288     * Add a "start"-button as primary.
289     * This is NOT regular behavior, but a special feature for the LegacyView
290     * of LearningSequence's sub-objects that do not implement a KioskModeView.
291     *
292     * The start-control is exclusively used to open an ILIAS-Object in a new windwow/tab.
293     */
294    public function start(string $label, string $url, int $parameter = null) : ControlBuilder
295    {
296        if ($this->start) {
297            throw new \LogicException("Only one start-control per view...", 1);
298        }
299
300        $this_cmd = $this->url_builder->getHref(self::CMD_START_OBJECT, 0);
301        $lp_cmd = str_replace(
302            '&cmd=view&',
303            '&cmd=' . self::CMD_CHECK_CURRENT_ITEM_LP . '&',
304            $this_cmd
305        );
306
307        $current_obj_id = $parameter;
308        $lp_cmd .= '&' . self::PARAM_LP_CURRENT_ITEM_OBJID . '=' . $current_obj_id;
309
310        $signal = $this->getStartSignal();
311        $this->setListenerJS($signal->getId(), $url, $lp_cmd, $this_cmd);
312
313        $this->start = $this->ui_factory->button()
314            ->primary($label, '')
315            ->withOnClick($signal);
316
317        return $this;
318    }
319
320    public function getStartControl()
321    {
322        return $this->start;
323    }
324
325    /**
326     * This is a hack and not supposed to be considered as a common way to inject
327     * JS-Code and bind it to a button.
328     * However, for the time beeing and the lack of a general alternative,
329     * I think it's OK to do this for legacy(!)-objects here...
330     */
331    protected function getStartSignal() : ILIAS\UI\Component\Signal
332    {
333        $id = uniqid();
334        $signal = new ILIAS\UI\Implementation\Component\Signal($id);
335        return $signal;
336    }
337
338
339    public function getAdditionalJS() : string
340    {
341        return $this->additional_js;
342    }
343
344    protected function setListenerJS(
345        string $signal_id,
346        string $new_win_url,
347        string $check_lp_url,
348        string $on_lp_change_url
349    ) {
350        $interval = $this->global_settings->getPollingIntervalMilliseconds();
351        $this->additional_js =
352<<<JS
353function lso_checkLPOfObject() {
354	$.ajax({
355		url: "$check_lp_url",
356	}).done(function(data) {
357		if(window._lso_current_item_lp === -1) {
358			window._lso_current_item_lp = data;
359		}
360		if (window._lso_current_item_lp !== data) {
361			location.replace('$on_lp_change_url');
362		}
363	});
364}
365
366$(document).on('{$signal_id}', function() {
367	var il_ls_win = window.open('$new_win_url');
368});
369window._lso_current_item_lp = -1;
370lso_checkLPOfObject();
371window.setInterval(lso_checkLPOfObject, $interval);
372JS;
373    }
374}
375