1<?php 2 3/* Copyright (c) 1998-2009 ILIAS open source, Extended GPL, see docs/LICENSE */ 4 5include_once "Services/Tracking/classes/class.ilLPObjSettings.php"; 6 7/** 8 * Base class for object lp connectors 9 * 10 * @author Jörg Lützenkirchen <luetzenkirchen@leifos.com> 11 * @version $Id: class.ilLPStatusPlugin.php 43734 2013-07-29 15:27:58Z jluetzen $ 12 * @package ServicesTracking 13 */ 14class ilObjectLP 15{ 16 /** 17 * @var ilTree 18 */ 19 protected $tree; 20 21 /** 22 * @var ilDB 23 */ 24 protected $db; 25 26 protected $obj_id; // [int] 27 protected $collection_instance; // [ilLPCollection] 28 protected $mode; // [int] 29 30 protected static $type_defaults; // [array] 31 32 protected function __construct($a_obj_id) 33 { 34 global $DIC; 35 36 $this->tree = $DIC->repositoryTree(); 37 $this->db = $DIC->database(); 38 $this->obj_id = (int) $a_obj_id; 39 } 40 41 /** 42 * @param int $a_obj_id 43 * @return ilObjectLP 44 */ 45 public static function getInstance($a_obj_id) 46 { 47 static $instances = array(); 48 49 if (!isset($instances[$a_obj_id])) { 50 $type = ilObject::_lookupType($a_obj_id); 51 $class = self::getTypeClass($type); 52 if ($class) { 53 $instance = new $class($a_obj_id); 54 } else { 55 // :TODO: should we return anything? 56 $instance = new self($a_obj_id); 57 } 58 $instances[$a_obj_id] = $instance; 59 } 60 61 return $instances[$a_obj_id]; 62 } 63 64 public static function getTypeClass($a_type) 65 { 66 global $DIC; 67 68 $objDefinition = $DIC["objDefinition"]; 69 70 if (self::isSupportedObjectType($a_type)) { 71 switch ($a_type) { 72 // container 73 74 case "crs": 75 include_once "Modules/Course/classes/class.ilCourseLP.php"; 76 return "ilCourseLP"; 77 78 case 'crsr': 79 return 'ilCourseReferenceLP'; 80 81 case "grp": 82 include_once "Modules/Group/classes/class.ilGroupLP.php"; 83 return "ilGroupLP"; 84 85 case "fold": 86 include_once "Modules/Folder/classes/class.ilFolderLP.php"; 87 return "ilFolderLP"; 88 89 case "lso": 90 include_once "Modules/LearningSequence/classes/LearnerProgress/class.ilLSLP.php"; 91 return "ilLSLP"; 92 93 94 // learning resources 95 96 case "lm": 97 include_once "Modules/LearningModule/classes/class.ilLearningModuleLP.php"; 98 return "ilLearningModuleLP"; 99 100 case "htlm": 101 include_once "Modules/HTMLLearningModule/classes/class.ilHTMLLearningModuleLP.php"; 102 return "ilHTMLLearningModuleLP"; 103 104 case "sahs": 105 include_once "Modules/ScormAicc/classes/class.ilScormLP.php"; 106 return "ilScormLP"; 107 108 109 // misc 110 111 case "tst": 112 include_once "Modules/Test/classes/class.ilTestLP.php"; 113 return "ilTestLP"; 114 115 case "exc": 116 include_once "Modules/Exercise/classes/class.ilExerciseLP.php"; 117 return "ilExerciseLP"; 118 119 case 'file': 120 require_once 'Modules/File/classes/class.ilFileLP.php'; 121 return 'ilFileLP'; 122 123 case "mcst": 124 require_once "Modules/MediaCast/classes/class.ilMediaCastLP.php"; 125 return "ilMediaCastLP"; 126 127 case "sess": 128 include_once "Modules/Session/classes/class.ilSessionLP.php"; 129 return "ilSessionLP"; 130 131 case "svy": 132 return "ilSurveyLP"; 133 134 case "prg": 135 include_once "Modules/StudyProgramme/classes/class.ilStudyProgrammeLP.php"; 136 return "ilStudyProgrammeLP"; 137 138 case "iass": 139 include_once "Modules/IndividualAssessment/classes/class.ilIndividualAssessmentLP.php"; 140 return "ilIndividualAssessmentLP"; 141 142 case "copa": 143 return "ilContentPageLP"; 144 145 case 'cmix': 146 return ilCmiXapiLP::class; 147 148 case 'lti': 149 return ilLTIConsumerLP::class; 150 151 // plugin 152 case $objDefinition->isPluginTypeName($a_type): 153 include_once "Services/Component/classes/class.ilPluginLP.php"; 154 return "ilPluginLP"; 155 } 156 } 157 } 158 public static function isSupportedObjectType($a_type) 159 { 160 global $DIC; 161 162 $objDefinition = $DIC["objDefinition"]; 163 164 $valid = array("crs", "grp", "fold", "lm", "htlm", "sahs", "tst", "exc", 165 "sess", "svy", "file", "mcst", "prg", "iass", "copa", "lso", 'cmix', 'lti', 'crsr'); 166 167 if (in_array($a_type, $valid)) { 168 return true; 169 } 170 171 if ($objDefinition->isPluginTypeName($a_type)) { 172 include_once 'Services/Repository/classes/class.ilRepositoryObjectPluginSlot.php'; 173 return ilRepositoryObjectPluginSlot::isTypePluginWithLP($a_type); 174 } 175 176 return false; 177 } 178 179 public function resetCaches() 180 { 181 $this->mode = null; 182 $this->collection_instance = null; 183 } 184 185 public function isAnonymized() 186 { 187 // see ilLPCollectionOfRepositoryObjects::validateEntry() 188 return false; 189 } 190 191 192 // 193 // MODE 194 // 195 196 public function getDefaultMode() 197 { 198 return ilLPObjSettings::LP_MODE_UNDEFINED; 199 } 200 201 public function getValidModes() 202 { 203 return array(); 204 } 205 206 public function getCurrentMode() 207 { 208 if ($this->mode === null) { 209 // using global type default if LP is inactive 210 include_once "Services/Tracking/classes/class.ilObjUserTracking.php"; 211 if (!ilObjUserTracking::_enabledLearningProgress()) { 212 $mode = self::getTypeDefaultFromDB(ilObject::_lookupType($this->obj_id)); 213 if ($mode === null) { 214 // fallback: inactive as type default may not be suitable 215 $mode = ilLPObjSettings::LP_MODE_DEACTIVATED; 216 } 217 } 218 // use object LP setting 219 else { 220 $mode = ilLPObjSettings::_lookupDBMode($this->obj_id); 221 if ($mode === null) { 222 // fallback: object type default 223 $mode = $this->getDefaultMode(); 224 } 225 } 226 $this->mode = (int) $mode; 227 } 228 229 return $this->mode; 230 } 231 232 public function isActive() 233 { 234 // :TODO: check LP activation? 235 236 $mode = $this->getCurrentMode(); 237 if ($mode == ilLPObjSettings::LP_MODE_DEACTIVATED || 238 $mode == ilLPObjSettings::LP_MODE_UNDEFINED) { 239 return false; 240 } 241 return true; 242 } 243 244 public function getModeText($a_mode) 245 { 246 return ilLPObjSettings::_mode2Text($a_mode); 247 } 248 249 public function getModeInfoText($a_mode) 250 { 251 return ilLPObjSettings::_mode2InfoText($a_mode); 252 } 253 254 public function getSettingsInfo() 255 { 256 // type-specific 257 } 258 259 260 // 261 // COLLECTION 262 // 263 264 public function getCollectionInstance() 265 { 266 if ($this->collection_instance === null) { 267 include_once "Services/Tracking/classes/collection/class.ilLPCollection.php"; 268 $this->collection_instance = ilLPCollection::getInstanceByMode($this->obj_id, $this->getCurrentMode()); 269 } 270 271 return $this->collection_instance; 272 } 273 274 275 // 276 // MEMBERS 277 // 278 279 public function getMembers($a_search = true) 280 { 281 $tree = $this->tree; 282 283 if (!$a_search) { 284 return; 285 } 286 287 $ref_ids = ilObject::_getAllReferences($this->obj_id); 288 $ref_id = current($ref_ids); 289 290 // walk path to find parent with specific members 291 $path = $tree->getPathId($ref_id); 292 array_pop($path); 293 foreach (array_reverse($path) as $path_ref_id) { 294 $olp = self::getInstance(ilObject::_lookupObjId($path_ref_id)); 295 $all = $olp->getMembers(false); 296 if (is_array($all)) { 297 return $all; 298 } 299 } 300 } 301 302 303 // 304 // RESET 305 // 306 307 final public function resetLPDataForCompleteObject($a_recursive = true) 308 { 309 $user_ids = $this->gatherLPUsers(); 310 if (sizeof($user_ids)) { 311 $this->resetLPDataForUserIds(array_unique($user_ids), $a_recursive); 312 } 313 } 314 315 final public function resetLPDataForUserIds(array $a_user_ids, $a_recursive = true) 316 { 317 if ((bool) $a_recursive && 318 method_exists($this, "getPossibleCollectionItems")) { // #15203 319 $subitems = $this->getPossibleCollectionItems(); 320 if (is_array($subitems)) { 321 foreach ($subitems as $sub_ref_id) { 322 $olp = self::getInstance(ilObject::_lookupObjId($sub_ref_id)); 323 $olp->resetLPDataForUserIds($a_user_ids, false); 324 } 325 } 326 } 327 328 $this->resetCustomLPDataForUserIds($a_user_ids, (bool) $a_recursive); 329 330 include_once "Services/Tracking/classes/class.ilLPMarks.php"; 331 ilLPMarks::_deleteForUsers($this->obj_id, $a_user_ids); 332 333 include_once "Services/Tracking/classes/class.ilChangeEvent.php"; 334 ilChangeEvent::_deleteReadEventsForUsers($this->obj_id, $a_user_ids); 335 336 // update LP status to get collections up-to-date 337 include_once "Services/Tracking/classes/class.ilLPStatusWrapper.php"; 338 foreach ($a_user_ids as $user_id) { 339 ilLPStatusWrapper::_updateStatus($this->obj_id, $user_id); 340 } 341 } 342 343 protected function resetCustomLPDataForUserIds(array $a_user_ids, $a_recursive = true) 344 { 345 // this should delete all data that is relevant for the supported LP modes 346 } 347 348 protected function gatherLPUsers() 349 { 350 include_once "Services/Tracking/classes/class.ilLPMarks.php"; 351 $user_ids = ilLPMarks::_getAllUserIds($this->obj_id); 352 353 include_once "Services/Tracking/classes/class.ilChangeEvent.php"; 354 $user_ids = array_merge($user_ids, ilChangeEvent::_getAllUserIds($this->obj_id)); 355 356 return $user_ids; 357 } 358 359 360 // 361 // EVENTS 362 // 363 364 final public static function handleMove($a_source_ref_id) 365 { 366 global $DIC; 367 368 $tree = $DIC->repositoryTree(); 369 $ilDB = $DIC->database(); 370 371 $ref_ids = $tree->getSubTreeIds($a_source_ref_id); 372 $ref_ids[] = $a_source_ref_id; 373 374 // get "parent" path to source node (not including source node) 375 $new_path = $tree->getPathId($a_source_ref_id); 376 array_pop($new_path); 377 $new_path = implode("/", $new_path); 378 379 include_once("./Services/Tracking/classes/class.ilLPStatusWrapper.php"); 380 381 // find collections with ref_ids 382 $set = $ilDB->query("SELECT DISTINCT(ut_lp_collections.obj_id) obj_id" . 383 " FROM object_reference" . 384 " JOIN ut_lp_collections ON" . 385 " (" . $ilDB->in("object_reference.ref_id", $ref_ids, "", "integer") . 386 " AND object_reference.ref_id = ut_lp_collections.item_id)"); 387 while ($rec = $ilDB->fetchAssoc($set)) { 388 if (in_array(ilObject::_lookupType($rec["obj_id"]), array("crs", "grp", "fold"))) { 389 $coll_ref_id = ilObject::_getAllReferences($rec["obj_id"]); 390 $coll_ref_id = array_pop($coll_ref_id); 391 392 // #13402 393 if ($coll_ref_id == $a_source_ref_id) { 394 continue; 395 } 396 397 // #17703 - collection has also been moved - nothing todo 398 if ($tree->isGrandChild($a_source_ref_id, $coll_ref_id)) { 399 continue; 400 } 401 402 // get path to collection (including collection "parent") 403 $coll_path = $tree->getPathId($coll_ref_id); 404 $coll_path = implode("/", $coll_path); 405 406 // collection path is not inside new path 407 if (!stristr($new_path, $coll_path)) { 408 // delete all items of moved (sub-)tree 409 $query = "DELETE FROM ut_lp_collections" . 410 " WHERE obj_id = " . $ilDB->quote($rec["obj_id"], "integer") . 411 " AND " . $ilDB->in("item_id", $ref_ids, "", "integer"); 412 $ilDB->manipulate($query); 413 414 ilLPStatusWrapper::_refreshStatus($rec["obj_id"]); 415 } 416 } 417 } 418 } 419 420 final public function handleToTrash() 421 { 422 $this->updateParentCollections(); 423 } 424 425 final public function handleDelete() 426 { 427 include_once "Services/Tracking/classes/class.ilLPMarks.php"; 428 ilLPMarks::deleteObject($this->obj_id); 429 430 include_once "Services/Tracking/classes/class.ilChangeEvent.php"; 431 ilChangeEvent::_delete($this->obj_id); 432 433 $collection = $this->getCollectionInstance(); 434 if ($collection) { 435 $collection->delete(); 436 } 437 438 $this->updateParentCollections(); 439 } 440 441 final protected function updateParentCollections() 442 { 443 $ilDB = $this->db; 444 445 include_once("./Services/Tracking/classes/class.ilLPStatusWrapper.php"); 446 447 // update parent collections? 448 $set = $ilDB->query("SELECT ut_lp_collections.obj_id obj_id FROM " . 449 "object_reference JOIN ut_lp_collections ON " . 450 "(object_reference.obj_id = " . $ilDB->quote($this->obj_id, "integer") . 451 " AND object_reference.ref_id = ut_lp_collections.item_id)"); 452 while ($rec = $ilDB->fetchAssoc($set)) { 453 if (in_array(ilObject::_lookupType($rec["obj_id"]), array("crs", "grp", "fold"))) { 454 // remove from parent collection 455 $query = "DELETE FROM ut_lp_collections" . 456 " WHERE obj_id = " . $ilDB->quote($rec["obj_id"], "integer") . 457 " AND item_id = " . $ilDB->quote($this->obj_id, "integer"); 458 $ilDB->manipulate($query); 459 460 ilLPStatusWrapper::_refreshStatus($rec["obj_id"]); 461 } 462 } 463 } 464 465 466 // 467 // LP-relevant memberships 468 // 469 470 /** 471 * Find (lp-relevant) members for given object ids 472 * 473 * @param array $a_res 474 * @param int $a_usr_id 475 * @param array $a_obj_ids 476 */ 477 protected static function isLPMember(array &$a_res, $a_usr_id, $a_obj_ids) 478 { 479 // should be overwritten by object-type-specific class 480 return false; 481 } 482 483 /** 484 * Find (lp-relevant) memberships by path 485 * 486 * @param array $a_res 487 * @param int $a_usr_id 488 * @param int $a_parent_ref_id 489 * @param array $a_obj_ids 490 * @param bool $a_mapped_ref_ids 491 * @return array 492 */ 493 protected static function findMembershipsByPath(array &$a_res, $a_usr_id, $a_parent_ref_id, array $a_obj_ids, $a_mapped_ref_ids = false) 494 { 495 global $DIC; 496 497 $tree = $DIC->repositoryTree(); 498 499 $found = array(); 500 501 // walk path to find course or group object and check members of that object 502 $path = $tree->getPathId($a_parent_ref_id); 503 foreach (array_reverse($path) as $path_ref_id) { 504 $type = ilObject::_lookupType($path_ref_id, true); 505 if ($type == "crs" || 506 $type == "grp") { 507 $class = self::getTypeClass($type); 508 $path_ob_id = ilObject::_lookupObjId($path_ref_id); 509 $chk = array(); 510 $class::isLPMember($chk, $a_usr_id, array($path_ob_id)); 511 if (!$a_mapped_ref_ids) { 512 // we found a grp/crs in path of (single) parent - mark all objects 513 foreach ($a_obj_ids as $obj_id) { 514 $found[] = $obj_id; 515 if ($chk[$path_ob_id]) { 516 $a_res[$obj_id] = true; 517 } 518 } 519 } else { 520 // all children from current node are "lp-valid" 521 foreach ($a_obj_ids as $obj_id => $ref_ids) { 522 foreach ($ref_ids as $ref_id) { 523 if ($tree->isGrandChild($path_ref_id, $ref_id)) { 524 $found[$obj_id][] = $ref_id; 525 if ($chk[$path_ob_id]) { 526 $a_res[$obj_id] = true; 527 } 528 break; 529 } 530 } 531 } 532 } 533 break; 534 } 535 } 536 537 return $found; 538 } 539 540 /** 541 * Get all objects where given user is member (from LP POV) 542 * 543 * @param int $a_usr_id 544 * @param array $a_obj_ids 545 * @param int $a_parent_ref_id 546 * @param bool $a_mapped_ref_ids 547 * @return array 548 */ 549 public static function getLPMemberships($a_usr_id, array $a_obj_ids, $a_parent_ref_id = null, $a_mapped_ref_ids = false) 550 { 551 global $DIC; 552 553 $ilDB = $DIC->database(); 554 $tree = $DIC->repositoryTree(); 555 556 // see ilTrQuery::getParticipantsForObject() [single object only] 557 // this is optimized for larger number of objects, e.g. list GUIs 558 559 $ref_map = []; 560 if ((bool) $a_mapped_ref_ids) { 561 $ref_map = $a_obj_ids; 562 $a_obj_ids = array_keys($a_obj_ids); 563 } 564 565 $res = array(); 566 567 // get object types 568 $types_map = array(); 569 $query = " SELECT obj_id, type" . 570 " FROM object_data" . 571 " WHERE " . $ilDB->in("obj_id", $a_obj_ids, "", "integer"); 572 $set = $ilDB->query($query); 573 while ($row = $ilDB->fetchAssoc($set)) { 574 $types_map[$row["type"]][] = $row["obj_id"]; 575 $res[$row["obj_id"]] = false; 576 } 577 578 $find_by_parent = array(); 579 foreach ($types_map as $type => $type_obj_ids) { 580 $class = self::getTypeClass($type); 581 if ($class) { 582 // lp-supported type? 583 if (!$class::isLPMember($res, $a_usr_id, $type_obj_ids)) { 584 $find_by_parent = array_merge($find_by_parent, $type_obj_ids); 585 } 586 } 587 } 588 589 if (sizeof($find_by_parent)) { 590 // single parent for all objects (repository/ilObjectListGUI) 591 if ($a_parent_ref_id) { 592 if (self::findMembershipsByPath($res, $a_usr_id, $a_parent_ref_id, $find_by_parent)) { 593 // we found a crs/grp in path, so no need to check read_events 594 $find_by_parent = null; 595 } 596 } 597 // different parents (PD > LP) 598 elseif (is_array($ref_map) && count($ref_map) > 0) { 599 foreach ($find_by_parent as $obj_id) { 600 // maybe already found by path search from other object/reference 601 if ($res[$obj_id] === false) { 602 if (isset($ref_map[$obj_id]) && is_array($ref_map[$obj_id])) { 603 // check all references 604 foreach ($ref_map[$obj_id] as $ref_id) { 605 $parent_ref_id = $tree->getParentId($ref_id); 606 if ($parent_ref_id == ROOT_FOLDER_ID) { 607 continue; 608 } 609 610 // we are checking the complete ref_map 611 // to find all relevant objects in subtree of current ref_id 612 $found = self::findMembershipsByPath($res, $a_usr_id, $parent_ref_id, $ref_map, true); 613 if (is_array($found) && count($found) > 0) { 614 // if any references were found in a crs/grp-subtree 615 // remove from "read-event"-last-resort-pool 616 foreach ($found as $found_obj_id => $found_ref_ids) { 617 $diff = array_diff($ref_map[$found_obj_id], $found_ref_ids); 618 if ($diff) { 619 // 1-n refs are in another subtree 620 // have to be checked separately 621 $ref_map[$found_obj_id] = $diff; 622 } else { 623 // all references found in subtree 624 // no need to check again 625 unset($ref_map[$found_obj_id]); 626 } 627 } 628 break; 629 } 630 } 631 } 632 } 633 } 634 635 $find_by_parent = array_keys($ref_map); 636 } 637 638 // last resort: use read_event? 639 if (is_array($find_by_parent) && count($find_by_parent) > 0) { 640 $set = $ilDB->query("SELECT obj_id" . 641 " FROM read_event" . 642 " WHERE " . $ilDB->in("obj_id", $find_by_parent, "", "integer") . 643 " AND usr_id = " . $ilDB->quote($a_usr_id, "integer")); 644 while ($row = $ilDB->fetchAssoc($set)) { 645 $res[$row["obj_id"]] = true; 646 } 647 } 648 } 649 650 return $res; 651 } 652 653 public function getMailTemplateId() 654 { 655 // type-specific 656 } 657 658 659 // 660 // type-specific support of features (should be enhanced) 661 // 662 663 public static function supportsSpentSeconds($a_obj_type) 664 { 665 return !in_array($a_obj_type, array("exc", "file", "mcst", "mob", "htlm", "copa", 'cmix', 'lti')); 666 } 667 668 public static function supportsMark($a_obj_type) 669 { 670 return !in_array($a_obj_type, array("lm", "dbk")); 671 } 672 673 public static function supportsMatrixView($a_obj_type) 674 { 675 return !in_array($a_obj_type, array('svy', 'tst', 'htlm', 'exc', 'sess', 'file', 'prg', 'copa', 'cmix', 'lti','crsr')); 676 } 677 678 679 // type-wide default 680 681 /** 682 * Get available type-specific default modes (no administration needed) 683 * @param bool $a_lp_active 684 * @return array 685 */ 686 public static function getDefaultModes($a_lp_active) 687 { 688 return array(ilLPObjSettings::LP_MODE_UNDEFINED); 689 } 690 691 protected static function getTypeDefaultFromDB($a_type) 692 { 693 global $DIC; 694 695 $ilDB = $DIC->database(); 696 697 if (!is_array(self::$type_defaults)) { 698 self::$type_defaults = array(); 699 $set = $ilDB->query("SELECT * FROM ut_lp_defaults"); 700 while ($row = $ilDB->fetchAssoc($set)) { 701 self::$type_defaults[$row["type_id"]] = $row["lp_mode"]; 702 } 703 } 704 return self::$type_defaults[$a_type]; 705 } 706 707 public static function saveTypeDefaults(array $a_data) 708 { 709 global $DIC; 710 711 $ilDB = $DIC->database(); 712 713 $ilDB->manipulate("DELETE FROM ut_lp_defaults"); 714 foreach ($a_data as $type => $mode) { 715 $ilDB->insert("ut_lp_defaults", array( 716 "type_id" => array("text", $type), 717 "lp_mode" => array("integer", $mode) 718 )); 719 } 720 } 721 722 /** 723 * Get current type default 724 * 725 * @param string $a_type 726 * @return int 727 */ 728 public static function getTypeDefault($a_type) 729 { 730 $db = self::getTypeDefaultFromDB($a_type); 731 if ($db !== null) { 732 return $db; 733 } 734 735 $class = self::getTypeClass($a_type); 736 $olp = new $class(0); 737 return $olp->getDefaultMode(); 738 } 739} 740