1<?php 2/* 3 +-----------------------------------------------------------------------------+ 4 | ILIAS open source | 5 +-----------------------------------------------------------------------------+ 6 | Copyright (c) 1998-2006 ILIAS open source, University of Cologne | 7 | | 8 | This program is free software; you can redistribute it and/or | 9 | modify it under the terms of the GNU General Public License | 10 | as published by the Free Software Foundation; either version 2 | 11 | of the License, or (at your option) any later version. | 12 | | 13 | This program is distributed in the hope that it will be useful, | 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 16 | GNU General Public License for more details. | 17 | | 18 | You should have received a copy of the GNU General Public License | 19 | along with this program; if not, write to the Free Software | 20 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | 21 +-----------------------------------------------------------------------------+ 22*/ 23 24include_once './Services/Container/classes/class.ilContainer.php'; 25include_once('Services/Container/classes/class.ilContainerSortingSettings.php'); 26 27/** 28* 29* @author Stefan Meyer <meyer@leifos.com> 30* @version $Id$ 31* 32* 33* @ingroup ServicesContainer 34*/ 35class ilContainerSorting 36{ 37 /** 38 * @var Logger 39 */ 40 protected $log; 41 42 /** 43 * @var ilTree 44 */ 45 protected $tree; 46 47 protected static $instances = array(); 48 49 protected $obj_id; 50 protected $db; 51 52 protected $sorting_settings = null; 53 const ORDER_DEFAULT = 999999; 54 55 /** 56 * Constructor 57 * 58 * @access private 59 * @param int obj_id 60 * 61 */ 62 private function __construct($a_obj_id) 63 { 64 global $DIC; 65 66 $this->log = $DIC["ilLog"]; 67 $this->tree = $DIC->repositoryTree(); 68 $ilDB = $DIC->database(); 69 70 $this->db = $ilDB; 71 $this->obj_id = $a_obj_id; 72 73 $this->read(); 74 } 75 76 /** 77 * Get sorting settings 78 * @return ilContainerSortingSettings 79 */ 80 public function getSortingSettings() 81 { 82 return $this->sorting_settings; 83 } 84 85 /** 86 * get instance by obj_id 87 * 88 * @access public 89 * @param int obj_id 90 * @return object ilContainerSorting 91 * @static 92 */ 93 public static function _getInstance($a_obj_id) 94 { 95 if (isset(self::$instances[$a_obj_id])) { 96 return self::$instances[$a_obj_id]; 97 } 98 return self::$instances[$a_obj_id] = new ilContainerSorting($a_obj_id); 99 } 100 101 /** 102 * Get positions of subitems 103 * @param int $a_obj_id 104 * @return 105 */ 106 public static function lookupPositions($a_obj_id) 107 { 108 global $DIC; 109 110 $ilDB = $DIC->database(); 111 112 $query = "SELECT * FROM container_sorting WHERE " . 113 "obj_id = " . $ilDB->quote($a_obj_id, 'integer'); 114 $res = $ilDB->query($query); 115 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { 116 $sorted[$row->child_id] = $row->position; 117 } 118 return $sorted ? $sorted : array(); 119 } 120 121 /** 122 * clone sorting 123 * 124 * @return 125 * @static 126 */ 127 public function cloneSorting($a_target_id, $a_copy_id) 128 { 129 $ilDB = $this->db; 130 131 $ilLog = ilLoggerFactory::getLogger("cont"); 132 $ilLog->debug("Cloning container sorting."); 133 134 $target_obj_id = ilObject::_lookupObjId($a_target_id); 135 136 include_once('./Services/CopyWizard/classes/class.ilCopyWizardOptions.php'); 137 $mappings = ilCopyWizardOptions::_getInstance($a_copy_id)->getMappings(); 138 139 140 // copy blocks sorting 141 $set = $ilDB->queryF( 142 "SELECT * FROM container_sorting_bl " . 143 " WHERE obj_id = %s ", 144 array("integer"), 145 array($this->obj_id) 146 ); 147 if ($rec = $ilDB->fetchAssoc($set)) { 148 if ($rec["block_ids"] != "") { 149 $ilLog->debug("Got block sorting for obj_id = " . $this->obj_id . ": " . $rec["block_ids"]); 150 $new_ids = implode(";", array_map(function ($block_id) use ($mappings) { 151 if (is_numeric($block_id)) { 152 $block_id = $mappings[$block_id]; 153 } 154 return $block_id; 155 }, explode(";", $rec["block_ids"]))); 156 157 $ilDB->replace("container_sorting_bl", 158 array("obj_id" => array("integer", $target_obj_id)), 159 array("block_ids" => array("text", $new_ids)) 160 ); 161 162 $ilLog->debug("Write block sorting for obj_id = " . $target_obj_id . ": " . $new_ids); 163 } 164 } 165 166 167 $ilLog->debug("Read container_sorting for obj_id = " . $this->obj_id); 168 169 $query = "SELECT * FROM container_sorting " . 170 "WHERE obj_id = " . $ilDB->quote($this->obj_id, 'integer'); 171 172 $res = $ilDB->query($query); 173 174 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { 175 if (!isset($mappings[$row->child_id]) or !$mappings[$row->child_id]) { 176 $ilLog->debug("No mapping found for child id:" . $row->child_id); 177 continue; 178 } 179 180 181 $new_parent_id = 0; 182 if ($row->parent_id) { 183 // see bug #20347 184 // at least in the case of sessions and item groups parent_ids in container sorting are object IDs but $mappings store references 185 if (in_array($row->parent_type, array("sess", "itgr"))) { 186 $par_refs = ilObject::_getAllReferences($row->parent_id); 187 $par_ref_id = current($par_refs); // should be only one 188 $ilLog->debug("Got ref id: " . $par_ref_id . " for obj_id " . $row->parent_id . " map ref id: " . $mappings[$par_ref_id] . "."); 189 if (isset($mappings[$par_ref_id])) { 190 $new_parent_ref_id = $mappings[$par_ref_id]; 191 $new_parent_id = ilObject::_lookupObjectId($new_parent_ref_id); 192 } 193 } else { // not sure if this is still used for other cases that expect ref ids 194 $new_parent_id = $mappings[$row->parent_id]; 195 } 196 if ((int) $new_parent_id == 0) { 197 $ilLog->debug("No mapping found for parent id:" . $row->parent_id . ", child_id: " . $row->child_id); 198 continue; 199 } 200 } 201 202 $query = "DELETE FROM container_sorting " . 203 "WHERE obj_id = " . $ilDB->quote($target_obj_id, 'integer') . " " . 204 "AND child_id = " . $ilDB->quote($mappings[$row->child_id], 'integer') . " " . 205 "AND parent_type = " . $ilDB->quote($row->parent_type, 'text') . ' ' . 206 "AND parent_id = " . $ilDB->quote((int) $new_parent_id, 'integer'); 207 $ilLog->debug($query); 208 $ilDB->manipulate($query); 209 210 // Add new value 211 $query = "INSERT INTO container_sorting (obj_id,child_id,position,parent_type,parent_id) " . 212 "VALUES( " . 213 $ilDB->quote($target_obj_id, 'integer') . ", " . 214 $ilDB->quote($mappings[$row->child_id], 'integer') . ", " . 215 $ilDB->quote($row->position, 'integer') . ", " . 216 $ilDB->quote($row->parent_type, 'text') . ", " . 217 $ilDB->quote((int) $new_parent_id, 'integer') . 218 ")"; 219 $ilLog->debug($query); 220 $ilDB->manipulate($query); 221 } 222 return true; 223 } 224 225 226 227 /** 228 * sort subitems 229 * 230 * @access public 231 * @param array item data 232 * @return array sorted item data 233 */ 234 public function sortItems($a_items) 235 { 236 if (!is_array($a_items)) { 237 return []; 238 } 239 240 $sorted = array(); 241 if ($this->getSortingSettings()->getSortMode() != ilContainer::SORT_MANUAL) { 242 switch ($this->getSortingSettings()->getSortMode()) { 243 case ilContainer::SORT_TITLE: 244 foreach ((array) $a_items as $type => $data) { 245 // #16311 - sorting will remove keys (prev/next) 246 if ($type == 'sess_link') { 247 $sorted[$type] = $data; 248 continue; 249 } 250 251 // this line used until #4389 has been fixed (3.10.6) 252 // reanimated with 4.4.0 253 $sorted[$type] = ilUtil::sortArray( 254 (array) $data, 255 'title', 256 ($this->getSortingSettings()->getSortDirection() == ilContainer::SORT_DIRECTION_ASC) ? 'asc' : 'desc', 257 false 258 ); 259 260 // the next line tried to use db sorting and has replaced sortArray due to bug #4389 261 // but leads to bug #12165. PHP should be able to do a proper sorting, if the locale 262 // is set correctly, so we witch back to sortArray (with 4.4.0) and see what 263 // feedback we get 264 // (next line has been used from 3.10.6 to 4.3.x) 265// $sorted[$type] = $data; 266 } 267 return $sorted ? $sorted : array(); 268 269 case ilContainer::SORT_ACTIVATION: 270 foreach ((array) $a_items as $type => $data) { 271 // #16311 - sorting will remove keys (prev/next) 272 if ($type == 'sess_link') { 273 $sorted[$type] = $data; 274 continue; 275 } 276 277 $sorted[$type] = ilUtil::sortArray( 278 (array) $data, 279 'start', 280 ($this->getSortingSettings()->getSortDirection() == ilContainer::SORT_DIRECTION_ASC) ? 'asc' : 'desc', 281 true 282 ); 283 } 284 return $sorted ? $sorted : array(); 285 286 287 case ilContainer::SORT_CREATION: 288 foreach ((array) $a_items as $type => $data) { 289 // #16311 - sorting will remove keys (prev/next) 290 if ($type == 'sess_link') { 291 $sorted[$type] = $data; 292 continue; 293 } 294 295 $sorted[$type] = ilUtil::sortArray( 296 (array) $data, 297 'create_date', 298 ($this->getSortingSettings()->getSortDirection() == ilContainer::SORT_DIRECTION_ASC) ? 'asc' : 'desc', 299 true 300 ); 301 } 302 return $sorted ? $sorted : array(); 303 } 304 return $a_items; 305 } 306 if (!is_array($a_items) || !count($a_items)) { 307 return $a_items; 308 } 309 $sorted = array(); 310 foreach ((array) $a_items as $type => $data) { 311 if ($type == 'sess_link') { 312 $sorted[$type] = $data; 313 continue; 314 } 315 316 // Add position 317 $items = array(); 318 foreach ((array) $data as $key => $item) { 319 $items[$key] = $item; 320 if (is_array($this->sorting['all']) and isset($this->sorting['all'][$item['child']])) { 321 $items[$key]['position'] = $this->sorting['all'][$item['child']]; 322 } else { 323 $items[$key]['position'] = self::ORDER_DEFAULT; 324 } 325 } 326 327 $items = $this->sortOrderDefault($items); 328 329 switch ($type) { 330 case '_all': 331 $sorted[$type] = ilUtil::sortArray((array) $items, 'position', 'asc', true); 332 break; 333 334 case '_non_sess': 335 $sorted[$type] = ilUtil::sortArray((array) $items, 'position', 'asc', true); 336 break; 337 338 default: 339 $sorted[$type] = ilUtil::sortArray((array) $items, 'position', 'asc', true); 340 break; 341 } 342 } 343 return $sorted ? $sorted : array(); 344 } 345 346 /** 347 * sort subitems (items of sessions or learning objectives) 348 * 349 * @access public 350 * @param 351 * @return 352 */ 353 public function sortSubItems($a_parent_type, $a_parent_id, $a_items) 354 { 355 switch ($this->getSortingSettings()->getSortMode()) { 356 case ilContainer::SORT_MANUAL: 357 $items = array(); 358 foreach ($a_items as $key => $item) { 359 $items[$key] = $item; 360 $items[$key]['position'] = isset($this->sorting[$a_parent_type][$a_parent_id][$item['child']]) ? 361 $this->sorting[$a_parent_type][$a_parent_id][$item['child']] : self::ORDER_DEFAULT; 362 } 363 364 $items = $this->sortOrderDefault($items); 365 return ilUtil::sortArray((array) $items, 'position', 'asc', true); 366 367 368 case ilContainer::SORT_ACTIVATION: 369 return ilUtil::sortArray( 370 (array) $a_items, 371 'start', 372 ($this->getSortingSettings()->getSortDirection() == ilContainer::SORT_DIRECTION_ASC) ? 'asc' : 'desc', 373 true 374 ); 375 376 case ilContainer::SORT_CREATION: 377 return ilUtil::sortArray( 378 (array) $a_items, 379 'create_date', 380 ($this->getSortingSettings()->getSortDirection() == ilContainer::SORT_DIRECTION_ASC) ? 'asc' : 'desc', 381 true 382 ); 383 384 default: 385 case ilContainer::SORT_TITLE: 386 return ilUtil::sortArray( 387 (array) $a_items, 388 'title', 389 ($this->getSortingSettings()->getSortDirection() == ilContainer::SORT_DIRECTION_ASC) ? 'asc' : 'desc', 390 false 391 ); 392 } 393 } 394 395 /** 396 * Save post 397 * 398 * @access public 399 * @param array of positions e.g array(crs => array(1,2,3),'lres' => array(3,5,6)) 400 * 401 */ 402 public function savePost($a_type_positions) 403 { 404 if (!is_array($a_type_positions)) { 405 return false; 406 } 407 $items = []; 408 foreach ($a_type_positions as $key => $position) { 409 if ($key == "blocks") { 410 $this->saveBlockPositions($position); 411 } elseif (!is_array($position)) { 412 $items[$key] = ((float) $position) * 100; 413 } else { 414 foreach ($position as $parent_id => $sub_items) { 415 $this->saveSubItems($key, $parent_id, $sub_items ? $sub_items : array()); 416 } 417 } 418 } 419 420 if (!count($items)) { 421 return $this->saveItems(array()); 422 } 423 424 asort($items); 425 $new_indexed = []; 426 $position = 0; 427 foreach ($items as $key => $null) { 428 $new_indexed[$key] = ++$position; 429 } 430 431 $this->saveItems($new_indexed); 432 } 433 434 435 /** 436 * save items 437 * 438 * @access protected 439 * @param string parent_type only used for sessions and objectives in the moment. Otherwise empty 440 * @param int parent id 441 * @param array array of items 442 * @return 443 */ 444 protected function saveItems($a_items) 445 { 446 $ilDB = $this->db; 447 448 foreach ($a_items as $child_id => $position) { 449 $ilDB->replace( 450 'container_sorting', 451 array( 452 'obj_id' => array('integer',$this->obj_id), 453 'child_id' => array('integer',$child_id), 454 'parent_id' => array('integer',0) 455 ), 456 array( 457 'parent_type' => array('text',''), 458 'position' => array('integer',$position) 459 ) 460 ); 461 } 462 return true; 463 } 464 465 /** 466 * Save subitem ordering (sessions, learning objectives) 467 * @param string $a_parent_type 468 * @param integer $a_parent_id 469 * @param array $a_items 470 * @return 471 */ 472 protected function saveSubItems($a_parent_type, $a_parent_id, $a_items) 473 { 474 $ilDB = $this->db; 475 476 foreach ($a_items as $child_id => $position) { 477 $ilDB->replace( 478 'container_sorting', 479 array( 480 'obj_id' => array('integer',$this->obj_id), 481 'child_id' => array('integer',$child_id), 482 'parent_id' => array('integer',$a_parent_id) 483 ), 484 array( 485 'parent_type' => array('text',$a_parent_type), 486 'position' => array('integer',$position) 487 ) 488 ); 489 } 490 return true; 491 } 492 493 /** 494 * Save block custom positions (for current object id) 495 * 496 * @param array $a_values 497 */ 498 protected function saveBlockPositions(array $a_values) 499 { 500 $ilDB = $this->db; 501 asort($a_values); 502 $ilDB->replace( 503 'container_sorting_bl', 504 array( 505 'obj_id' => array('integer',$this->obj_id) 506 ), 507 array( 508 'block_ids' => array('text', implode(";", array_keys($a_values))) 509 ) 510 ); 511 } 512 513 /** 514 * Read block custom positions (for current object id) 515 * 516 * @return array 517 */ 518 public function getBlockPositions() 519 { 520 $ilDB = $this->db; 521 522 $set = $ilDB->query("SELECT block_ids" . 523 " FROM container_sorting_bl" . 524 " WHERE obj_id = " . $ilDB->quote($this->obj_id, "integer")); 525 $row = $ilDB->fetchAssoc($set); 526 if ($row["block_ids"]) { 527 return explode(";", $row["block_ids"]); 528 } 529 } 530 531 532 /** 533 * Read 534 * 535 * @access private 536 * 537 */ 538 private function read() 539 { 540 $tree = $this->tree; 541 542 if (!$this->obj_id) { 543 $this->sorting_settings = new ilContainerSortingSettings(); 544 return true; 545 } 546 547 $sorting_settings = ilContainerSortingSettings::getInstanceByObjId($this->obj_id); 548 $this->sorting_settings = $sorting_settings->loadEffectiveSettings(); 549 $query = "SELECT * FROM container_sorting " . 550 "WHERE obj_id = " . $this->db->quote($this->obj_id, 'integer') . " ORDER BY position"; 551 $res = $this->db->query($query); 552 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) { 553 if ($row->parent_id) { 554 $this->sorting[$row->parent_type][$row->parent_id][$row->child_id] = $row->position; 555 } else { 556 $this->sorting['all'][$row->child_id] = $row->position; 557 } 558 } 559 return true; 560 } 561 562 /** 563 * Position and order sort order for new object without position in manual sorting type 564 * 565 * @param $items 566 * @return array 567 */ 568 private function sortOrderDefault($items) 569 { 570 $no_position = array(); 571 572 foreach ($items as $key => $item) { 573 if ($item["position"] == self::ORDER_DEFAULT) { 574 $no_position[] = array("key" => $key, "title" => $item["title"], "create_date" => $item["create_date"], 575 "start" => $item["start"]); 576 } 577 } 578 579 if (!count($no_position)) { 580 return $items; 581 } 582 583 switch ($this->getSortingSettings()->getSortNewItemsOrder()) { 584 case ilContainer::SORT_NEW_ITEMS_ORDER_TITLE: 585 $no_position = ilUtil::sortArray( 586 (array) $no_position, 587 'title', 588 ($this->getSortingSettings()->getSortDirection() == ilContainer::SORT_DIRECTION_ASC) ? 'asc' : 'desc', 589 true 590 ); 591 break; 592 case ilContainer::SORT_NEW_ITEMS_ORDER_CREATION: 593 $no_position = ilUtil::sortArray( 594 (array) $no_position, 595 'create_date', 596 ($this->getSortingSettings()->getSortDirection() == ilContainer::SORT_DIRECTION_ASC) ? 'asc' : 'desc', 597 true 598 ); 599 break; 600 case ilContainer::SORT_NEW_ITEMS_ORDER_ACTIVATION: 601 $no_position = ilUtil::sortArray( 602 (array) $no_position, 603 'start', 604 ($this->getSortingSettings()->getSortDirection() == ilContainer::SORT_DIRECTION_ASC) ? 'asc' : 'desc', 605 true 606 ); 607 608 } 609 $count = $this->getSortingSettings()->getSortNewItemsPosition() 610 == ilContainer::SORT_NEW_ITEMS_POSITION_TOP ? -900000 : 900000; 611 612 foreach ($no_position as $values) { 613 $items[$values["key"]]["position"] = $count; 614 $count++; 615 } 616 return $items; 617 } 618} 619