1<?php
2/* Copyright (c) 1998-2010 ILIAS open source, Extended GPL, see docs/LICENSE */
3require_once('./Services/Repository/classes/class.ilObjectPlugin.php');
4
5/**
6* Class ilContainerRenderer
7*
8* @author Jörg Lützenkirchen  <luetzenkirchen@leifos.com>
9* @version $Id: class.ilContainerGUI.php 52026 2014-08-05 10:22:06Z smeyer $
10*/
11class ilContainerRenderer
12{
13    /**
14     * @var ilLanguage
15     */
16    protected $lng;
17
18    /**
19     * @var ilSetting
20     */
21    protected $settings;
22
23    /**
24     * @var ilObjectDefinition
25     */
26    protected $obj_definition;
27
28    // switches
29    protected $enable_manage_select_all; // [bool]
30    protected $enable_multi_download; // [bool]
31    protected $active_block_ordering; // [bool]
32
33    // properties
34    protected $type_blocks = array(); // [array]
35    protected $custom_blocks = array(); // [array]
36    protected $items = array(); // [array]
37    protected $hidden_items = array(); // [array]
38    protected $block_items = array(); // [array]
39    protected $details = array(); // [array]
40    protected $item_ids = array(); // [array]
41
42    // block (unique) ids
43    protected $rendered_blocks = array(); // [array]
44    protected $bl_cnt = 0; // [int]
45    protected $cur_row_type; // [string]
46
47    // ordering
48    protected $block_pos = array(); // [array]
49    protected $block_custom_pos = array(); // [array]
50    protected $order_cnt = 0; // [int]
51
52    /**
53     * @var array
54     */
55    protected $show_more = [];
56
57    const UNIQUE_SEPARATOR = "-";
58
59    /**
60     * @var int
61     */
62    protected $view_mode;
63
64    /**
65     * @var \ILIAS\DI\UIServices
66     */
67    protected $ui;
68
69    /**
70     * @var ilCtrl
71     */
72    protected $ctrl;
73
74    /**
75     * Constructor
76     *
77     * @param bool $a_enable_manage_select_all
78     * @param bool $a_enable_multi_download
79     * @param bool $a_active_block_ordering
80     * @param array $a_block_custom_positions
81     */
82    public function __construct($a_enable_manage_select_all = false, $a_enable_multi_download = false, $a_active_block_ordering = false, $a_block_custom_positions, $container_gui_obj, $a_view_mode =
83        ilContainerContentGUI::VIEW_MODE_LIST)
84    {
85        global $DIC;
86
87        $this->lng = $DIC->language();
88        $this->settings = $DIC->settings();
89        $this->ui = $DIC->ui();
90        $this->obj_definition = $DIC["objDefinition"];
91        $this->enable_manage_select_all = (bool) $a_enable_manage_select_all;
92        $this->enable_multi_download = (bool) $a_enable_multi_download;
93        $this->active_block_ordering = (bool) $a_active_block_ordering;
94        $this->block_custom_pos = $a_block_custom_positions;
95        $this->view_mode = $a_view_mode;
96        $this->container_gui = $container_gui_obj;
97        $this->ctrl = $DIC->ctrl();
98    }
99
100    /**
101     * Get view mode
102     */
103    protected function getViewMode()
104    {
105        return $this->view_mode;
106    }
107
108    //
109    // blocks
110    //
111
112    /**
113     * Add type block
114     *
115     * @param string $a_type repository object type
116     * @param string $a_prefix html snippet
117     * @param string $a_postfix html snippet
118     * @return boolean
119     */
120    public function addTypeBlock($a_type, $a_prefix = null, $a_postfix = null)
121    {
122        if ($a_type != "itgr" &&
123            !$this->hasTypeBlock($a_type)) {
124            $this->type_blocks[$a_type] = array(
125                "prefix" => $a_prefix
126                ,"postfix" => $a_postfix
127            );
128            return true;
129        }
130        return false;
131    }
132
133    /**
134     * Type block already exists?
135     *
136     * @param string $a_type repository object type
137     * @return bool
138     */
139    public function hasTypeBlock($a_type)
140    {
141        return array_key_exists($a_type, $this->type_blocks);
142    }
143
144    /**
145     * Add custom block
146     *
147     * @param mixed $a_id
148     * @param string $a_caption
149     * @param string $a_actions html snippet
150     * @return boolean
151     */
152    public function addCustomBlock($a_id, $a_caption, $a_actions = null, $a_data = array())
153    {
154        if (!$this->hasCustomBlock($a_id)) {
155            $this->custom_blocks[$a_id] = array(
156                "caption" => $a_caption
157                ,"actions" => $a_actions
158                ,"data" => $a_data
159            );
160            return true;
161        }
162        return false;
163    }
164
165    /**
166     * Custom block already exists?
167     *
168     * @param mixed $a_id
169     * @return bool
170     */
171    public function hasCustomBlock($a_id)
172    {
173        return array_key_exists($a_id, $this->custom_blocks);
174    }
175
176    /**
177     * Any block with id exists?
178     *
179     * @param mixed $a_id
180     * @return bool
181     */
182    public function isValidBlock($a_id)
183    {
184        return ($this->hasTypeBlock($a_id) ||
185            $this->hasCustomBlock($a_id));
186    }
187
188
189    //
190    // items
191    //
192
193    /**
194     * Mark item id as used, but do not render
195     *
196     * @param mixed $a_id
197     */
198    public function hideItem($a_id)
199    {
200        // see hasItem();
201        $this->hidden_items[$a_id] = true;
202
203        // #16629 - do not remove hidden items from other blocks
204        // $this->removeItem($a_id);
205    }
206
207    /**
208     * Remove item (from any block)
209     *
210     * @param mixed $a_id
211     */
212    public function removeItem($a_id)
213    {
214        if (!$this->hasItem($a_id)) {
215            return;
216        }
217
218        unset($this->item_ids[$a_id]);
219        unset($this->hidden_items[$a_id]);
220
221        foreach (array_keys($this->items) as $item_id) {
222            if (array_pop(explode(self::UNIQUE_SEPARATOR, $item_id)) == $a_id) {
223                unset($this->items[$item_id]);
224            }
225        }
226
227        foreach ($this->block_items as $block_id => $items) {
228            foreach ($items as $idx => $item_id) {
229                if (array_pop(explode(self::UNIQUE_SEPARATOR, $item_id)) == $a_id) {
230                    unset($this->block_items[$block_id][$idx]);
231                    if (!sizeof($this->block_items[$block_id])) {
232                        unset($this->block_items[$block_id]);
233                    }
234                    break;
235                }
236            }
237        }
238    }
239
240    /**
241     * Item with id exists?
242     *
243     * @param mixed $a_id
244     * @return bool
245     */
246    public function hasItem($a_id)
247    {
248        return (array_key_exists($a_id, $this->item_ids) ||
249            array_key_exists($a_id, $this->hidden_items));
250    }
251
252    /**
253     * Add item to existing block
254     *
255     * @param mixed $a_block_id
256     * @param string $a_item_type repository object type
257     * @param mixed $a_item_id
258     * @param string $a_item_html html snippet
259     * @param bool $a_force enable multiple rendering
260     * @return boolean
261     */
262    public function addItemToBlock($a_block_id, $a_item_type, $a_item_id, $a_item_html, $a_force = false)
263    {
264        if ($this->isValidBlock($a_block_id) &&
265            $a_item_type != "itgr" &&
266            (!$this->hasItem($a_item_id) || $a_force)) {
267            if (is_string($a_item_html) && trim($a_item_html) == "") {
268                return false;
269            }
270            if (!$a_item_html) {
271                return false;
272            }
273
274
275            // #16563 - item_id (== ref_id) is NOT unique, adding parent block id
276            $uniq_id = $a_block_id . self::UNIQUE_SEPARATOR . $a_item_id;
277
278            $this->items[$uniq_id] = array(
279                "type" => $a_item_type
280                ,"html" => $a_item_html
281            );
282
283            // #18326
284            $this->item_ids[$a_item_id] = true;
285
286            $this->block_items[$a_block_id][] = $uniq_id;
287            return true;
288        }
289        return false;
290    }
291
292    /**
293     * Add show more button to a block
294     */
295    public function addShowMoreButton($a_block_id)
296    {
297        $this->show_more[] = $a_block_id;
298    }
299
300    /**
301     * Add details level
302     *
303     * @param int $a_level
304     * @param string $a_url
305     * @param bool $a_active
306     */
307    public function addDetailsLevel($a_level, $a_url, $a_active = false)
308    {
309        $this->details[$a_level] = array(
310            "url" => $a_url
311            ,"active" => (bool) $a_active
312        );
313    }
314
315    /**
316     * Reset/remove all detail levels
317     */
318    public function resetDetails()
319    {
320        $this->details = array();
321    }
322
323
324    //
325    // render
326    //
327
328    /**
329     * Set block position
330     *
331     * @param mixed $a_block_id
332     * @param int $a_pos
333     */
334    public function setBlockPosition($a_block_id, $a_pos)
335    {
336        if ($this->isValidBlock($a_block_id)) {
337            $this->block_pos[$a_block_id] = $a_pos;
338        }
339    }
340
341    /**
342     * Get rendered html (of all blocks)
343     *
344     * @return string
345     */
346    public function getHTML()
347    {
348        $valid = false;
349
350        $block_tpl = $this->initBlockTemplate();
351
352        foreach ($this->processBlockPositions() as $block_id) {
353            if (array_key_exists($block_id, $this->custom_blocks)) {
354                if ($this->renderHelperCustomBlock($block_tpl, $block_id)) {
355                    $this->addSeparatorRow($block_tpl);
356                    $valid = true;
357                }
358            }
359            if (array_key_exists($block_id, $this->type_blocks)) {
360                if ($this->renderHelperTypeBlock($block_tpl, $block_id)) {
361                    $this->addSeparatorRow($block_tpl);
362                    $valid = true;
363                }
364            }
365        }
366
367        if ($valid) {
368            $this->renderDetails($block_tpl);
369
370            return $block_tpl->get();
371        }
372    }
373
374    /**
375     * Get rendered html of single type block
376     *
377     * @param string $a_type repository object type
378     * @return html
379     */
380    public function renderSingleTypeBlock($a_type)
381    {
382        $block_tpl = $this->initBlockTemplate();
383
384        if ($this->renderHelperTypeBlock($block_tpl, $a_type, true)) {
385            return $block_tpl->get();
386        }
387    }
388
389    /**
390     * Get rendered html of single custom block
391     *
392     * @param mixed $a_id
393     * @return html
394     */
395    public function renderSingleCustomBlock($a_id)
396    {
397        $block_tpl = $this->initBlockTemplate();
398
399        if ($this->renderHelperCustomBlock($block_tpl, $a_id, true)) {
400            return $block_tpl->get();
401        }
402    }
403
404
405    //
406    // render (helper)
407    //
408
409    /**
410     * Process block positions
411     *
412     * @return array block ids
413     */
414    protected function processBlockPositions()
415    {
416        // manual order
417        if (is_array($this->block_custom_pos) && sizeof($this->block_custom_pos)) {
418            $tmp = $this->block_pos;
419            $this->block_pos = array();
420            foreach ($this->block_custom_pos as $idx => $block_id) {
421                if ($this->isValidBlock($block_id)) {
422                    $this->block_pos[$block_id] = $idx;
423                }
424            }
425
426            // at least some manual are valid
427            if (sizeof($this->block_pos)) {
428                // append missing blocks from default order
429                $last = max($this->block_pos);
430                foreach (array_keys($tmp) as $block_id) {
431                    if (!array_key_exists($block_id, $this->block_pos)) {
432                        $this->block_pos[$block_id] = ++$last;
433                    }
434                }
435            }
436            // all manual invalid, use default
437            else {
438                $this->block_pos = $tmp;
439            }
440        }
441
442        // add missing blocks to order
443        $last = sizeof($this->block_pos)
444            ? max($this->block_pos)
445            : 0;
446        foreach (array_keys($this->custom_blocks) as $block_id) {
447            if (!array_key_exists($block_id, $this->block_pos)) {
448                $this->block_pos[$block_id] = ++$last;
449            }
450        }
451        foreach (array_keys($this->type_blocks) as $block_id) {
452            if (!array_key_exists($block_id, $this->block_pos)) {
453                $this->block_pos[$block_id] = ++$last;
454            }
455        }
456
457        asort($this->block_pos);
458
459        return array_keys($this->block_pos);
460    }
461
462    /**
463     * Render custom block
464     *
465     * @param ilTemplate $a_block_tpl
466     * @param mixed $a_block_id
467     * @param bool $a_is_single
468     * @return boolean
469     */
470    protected function renderHelperCustomBlock(ilTemplate $a_block_tpl, $a_block_id, $a_is_single = false)
471    {
472        if ($this->hasCustomBlock($a_block_id)) {
473            return $this->renderHelperGeneric($a_block_tpl, $a_block_id, $this->custom_blocks[$a_block_id], $a_is_single);
474        }
475        return false;
476    }
477
478    /**
479     * Render type block
480     *
481     * @param ilTemplate $a_block_tpl
482     * @param string $a_type repository object type
483     * @param bool $a_is_single
484     * @return boolean
485     */
486    protected function renderHelperTypeBlock(ilTemplate $a_block_tpl, $a_type, $a_is_single = false)
487    {
488        if ($this->hasTypeBlock($a_type)) {
489            $block = $this->type_blocks[$a_type];
490            $block["type"] = $a_type;
491            return $this->renderHelperGeneric($a_block_tpl, $a_type, $block, $a_is_single);
492        }
493        return false;
494    }
495
496    /**
497     * Render block
498     *
499     * @param ilTemplate $a_block_tpl
500     * @param mixed $a_block_id
501     * @param array $a_block block properties
502     * @param bool $a_is_single
503     * @return boolean
504     */
505    protected function renderHelperGeneric(ilTemplate $a_block_tpl, $a_block_id, array $a_block, $a_is_single = false)
506    {
507        $ctrl = $this->ctrl;
508        if (!in_array($a_block_id, $this->rendered_blocks)) {
509            $this->rendered_blocks[] = $a_block_id;
510
511            $block_types = array();
512            if (is_array($this->block_items[$a_block_id])) {
513                foreach ($this->block_items[$a_block_id] as $item_id) {
514                    if (isset($this->items[$item_id]["type"])) {
515                        $block_types[] = $this->items[$item_id]["type"];
516                    }
517                }
518            }
519
520            // #14610 - manage empty item groups
521            if (is_array($this->block_items[$a_block_id]) ||
522                is_numeric($a_block_id)) {
523                $cards = [];
524
525                $order_id = (!$a_is_single && $this->active_block_ordering)
526                    ? $a_block_id
527                    : null;
528                $this->addHeaderRow($a_block_tpl, $a_block["type"], $a_block["caption"], array_unique($block_types), $a_block["actions"], $order_id, $a_block["data"]);
529
530                if ($this->getViewMode() == ilContainerContentGUI::VIEW_MODE_LIST) {
531                    if ($a_block["prefix"]) {
532                        $this->addStandardRow($a_block_tpl, $a_block["prefix"]);
533                    }
534                }
535
536                if (is_array($this->block_items[$a_block_id])) {
537                    foreach ($this->block_items[$a_block_id] as $item_id) {
538                        if ($this->getViewMode() == ilContainerContentGUI::VIEW_MODE_LIST) {
539                            $this->addStandardRow($a_block_tpl, $this->items[$item_id]["html"], $item_id);
540                        } else {
541                            $cards[] = $this->items[$item_id]["html"];
542                        }
543                    }
544                }
545
546                if ($this->getViewMode() == ilContainerContentGUI::VIEW_MODE_LIST) {
547                    if ($a_block["postfix"]) {
548                        $this->addStandardRow($a_block_tpl, $a_block["postfix"]);
549                    }
550                }
551
552                if ($this->getViewMode() == ilContainerContentGUI::VIEW_MODE_TILE) {
553                    $f = $this->ui->factory();
554                    $renderer = $this->ui->renderer();
555
556                    //Create a deck with large cards
557                    $deck = $f->deck($cards)->withNormalCardsSize();
558                    //$deck = $f->deck($cards)->withSmallCardsSize();
559
560
561                    $html = $renderer->render($deck);
562                    $a_block_tpl->setCurrentBlock("tile_rows");
563                    $a_block_tpl->setVariable("TILE_ROWS", $html);
564                    $a_block_tpl->parseCurrentBlock();
565                }
566
567                // show more
568                if (in_array($a_block_id, $this->show_more)) {
569                    $a_block_tpl->setCurrentBlock("show_more");
570
571                    $ctrl->setParameter($this->container_gui, "type", $a_block_id);
572                    $url = $ctrl->getLinkTarget($this->container_gui, "renderBlockAsynch", "", true);
573                    $ctrl->setParameter($this->container_gui, "type", "");
574
575                    $f = $this->ui->factory();
576                    $renderer = $this->ui->renderer();
577                    $button = $f->button()->standard($this->lng->txt("cont_show_more"), "")
578                        ->withLoadingAnimationOnClick(true)
579                        ->withOnLoadCode(function ($id) use ($a_block_id, $url) {
580                            return "il.Container.initShowMore('$id', '$a_block_id', '" . $url . "');";
581                        });
582                    if ($ctrl->isAsynch()) {
583                        $a_block_tpl->setVariable("SHOW_MORE_BUTTON", $renderer->renderAsync($button));
584                    } else {
585                        $a_block_tpl->setVariable("SHOW_MORE_BUTTON", $renderer->render($button));
586                    }
587                    $a_block_tpl->parseCurrentBlock();
588                    $a_block_tpl->setCurrentBlock("show_more");
589                    $a_block_tpl->parseCurrentBlock();
590                }
591
592                return true;
593            }
594        }
595
596        return false;
597    }
598
599    /**
600     * Init template
601     *
602     * @return ilTemplate
603     */
604    protected function initBlockTemplate()
605    {
606        // :TODO: obsolete?
607        $this->cur_row_type = "row_type_1";
608
609        return new ilTemplate("tpl.container_list_block.html", true, true, "Services/Container");
610    }
611
612    /**
613     * Render block header
614     *
615     * @param ilTemplate $a_tpl
616     * @param string $a_type
617     * @param string $a_text
618     * @param array $a_types_in_block
619     * @param string $a_commands_html
620     * @param int $a_order_id
621     */
622    protected function addHeaderRow(ilTemplate $a_tpl, $a_type = "", $a_text = "", array $a_types_in_block = null, $a_commands_html = null, $a_order_id = null, $a_data = array())
623    {
624        $lng = $this->lng;
625        $ilSetting = $this->settings;
626        $objDefinition = $this->obj_definition;
627
628        $a_tpl->setVariable("CB_ID", ' id="bl_cntr_' . (++$this->bl_cnt) . '"');
629
630        if ($this->enable_manage_select_all) {
631            $this->renderSelectAllBlock($a_tpl);
632        } elseif ($this->enable_multi_download) {
633            if ($a_type) {
634                $a_types_in_block = array($a_type);
635            }
636            foreach ($a_types_in_block as $type) {
637                if (in_array($type, $this->getDownloadableTypes())) {
638                    $this->renderSelectAllBlock($a_tpl);
639                    break;
640                }
641            }
642        }
643
644        if ($a_text == "" && $a_type != "") {
645            if (!$objDefinition->isPlugin($a_type)) {
646                $title = $lng->txt("objs_" . $a_type);
647            } else {
648                include_once("./Services/Component/classes/class.ilPlugin.php");
649                $pl = ilObjectPlugin::getPluginObjectByType($a_type);
650                $title = $pl->txt("objs_" . $a_type);
651            }
652        } else {
653            $title = $a_text;
654        }
655
656        include_once("./Modules/ItemGroup/classes/class.ilItemGroupBehaviour.php");
657        if (is_array($a_data)) {
658            foreach ($a_data as $k => $v) {
659                $a_tpl->setCurrentBlock("cb_data");
660                $a_tpl->setVariable("DATA_KEY", $k);
661                $a_tpl->setVariable("DATA_VALUE", $v);
662                $a_tpl->parseCurrentBlock();
663
664                if ($k == "behaviour" && $v == ilItemGroupBehaviour::EXPANDABLE_CLOSED) {
665                    $a_tpl->touchBlock("container_items_hide");
666                }
667            }
668        }
669
670        if ($ilSetting->get("icon_position_in_lists") != "item_rows" &&
671            $a_type != "") {
672            $icon = ilUtil::getImagePath("icon_" . $a_type . ".svg");
673
674            $a_tpl->setCurrentBlock("container_header_row_image");
675            $a_tpl->setVariable("HEADER_IMG", $icon);
676            $a_tpl->setVariable("HEADER_ALT", $title);
677        } else {
678            $a_tpl->setCurrentBlock("container_header_row");
679        }
680
681        if ($a_order_id) {
682            $a_tpl->setVariable("BLOCK_HEADER_ORDER_NAME", "position[blocks][" . $a_order_id . "]");
683            $a_tpl->setVariable("BLOCK_HEADER_ORDER_NUM", (++$this->order_cnt) * 10);
684        }
685
686        $a_tpl->setVariable("BLOCK_HEADER_CONTENT", $title);
687        $a_tpl->setVariable("CHR_COMMANDS", $a_commands_html);
688        $a_tpl->parseCurrentBlock();
689
690        //$a_tpl->touchBlock("container_row");
691
692        $this->resetRowType();
693    }
694
695    /**
696     * Render item row
697     *
698     * @param ilTemplate $a_tpl
699     * @param string $a_html
700     * @param int $a_ref_id
701     */
702    protected function addStandardRow(ilTemplate $a_tpl, $a_html, $a_ref_id = 0)
703    {
704        // :TODO: obsolete?
705        $this->cur_row_type = ($this->cur_row_type == "row_type_1")
706            ? "row_type_2"
707            : "row_type_1";
708
709        if ($a_ref_id > 0) {
710            $a_tpl->setCurrentBlock($this->cur_row_type);
711            $a_tpl->setVariable("ROW_ID", 'id="item_row_' . $a_ref_id . '"');
712            $a_tpl->parseCurrentBlock();
713        } else {
714            $a_tpl->touchBlock($this->cur_row_type);
715        }
716
717        $a_tpl->setCurrentBlock("container_standard_row");
718        $a_tpl->setVariable("BLOCK_ROW_CONTENT", $a_html);
719        $a_tpl->parseCurrentBlock();
720
721        $a_tpl->touchBlock("container_row");
722    }
723
724    /**
725     * Render "select all"
726     */
727    protected function renderSelectAllBlock(ilTemplate $a_tpl)
728    {
729        $lng = $this->lng;
730
731        $a_tpl->setCurrentBlock("select_all_row");
732        $a_tpl->setVariable("CHECKBOXNAME", "bl_cb_" . $this->bl_cnt);
733        $a_tpl->setVariable("SEL_ALL_PARENT", "bl_cntr_" . $this->bl_cnt);
734        $a_tpl->setVariable("SEL_ALL_PARENT", "bl_cntr_" . $this->bl_cnt);
735        $a_tpl->setVariable("TXT_SELECT_ALL", $lng->txt("select_all"));
736        $a_tpl->parseCurrentBlock();
737    }
738
739    /**
740     * Render separator row
741     *
742     * @param ilTemplate $a_tpl
743     */
744    protected function addSeparatorRow(ilTemplate $a_tpl)
745    {
746        $a_tpl->setCurrentBlock("container_block");
747        $a_tpl->parseCurrentBlock();
748    }
749
750    /**
751     * Reset internal row type
752     */
753    protected function resetRowType()
754    {
755        // :TODO: obsolete?
756        $this->cur_row_type = "";
757    }
758
759    /**
760     * Get downloadable repository object types
761     *
762     * @return array
763     */
764    protected function getDownloadableTypes()
765    {
766        return array("fold", "file");
767    }
768
769    /**
770     * Render detail level
771     *
772     * @param ilTemplate $a_tpl
773     */
774    public function renderDetails(ilTemplate $a_tpl)
775    {
776        $lng = $this->lng;
777
778        if (sizeof($this->details)) {
779            $a_tpl->setCurrentBlock('container_details_row');
780            $a_tpl->setVariable('TXT_DETAILS', $lng->txt('details'));
781            $a_tpl->parseCurrentBlock();
782        }
783    }
784}
785