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