1<?php 2/* Copyright (c) 1998-2009 ILIAS open source, Extended GPL, see docs/LICENSE */ 3 4include_once("./Services/Object/classes/class.ilObjectFactory.php"); 5 6/** 7* Repository Utilities (application layer, put GUI related stuff into ilRepUtilGUI) 8* 9* @author Alex Killing <alex.killing@gmx.de> 10* @version $Id$ 11* @ingroup ServicesRepository 12*/ 13class ilRepUtil 14{ 15 /** 16 * @var ilDB 17 */ 18 protected $db; 19 20 /** 21 * @var ilTree 22 */ 23 protected $tree; 24 25 /** 26 * @var ilSetting 27 */ 28 protected $settings; 29 30 31 /** 32 * Constructor 33 */ 34 public function __construct() 35 { 36 global $DIC; 37 38 $this->db = $DIC->database(); 39 $this->tree = $DIC->repositoryTree(); 40 $this->settings = $DIC->settings(); 41 } 42 43 /** 44 * Delete objects. Move them to trash (if trash feature is enabled). 45 * @param int current ref id 46 * @param int[] array of ref(!) ids to be deleted 47 * @throws \ilRepositoryException on missing permission, objects already deleted,... 48 */ 49 public static function deleteObjects($a_cur_ref_id, $a_ids) 50 { 51 global $DIC; 52 53 $ilAppEventHandler = $DIC["ilAppEventHandler"]; 54 $rbacsystem = $DIC->rbac()->system(); 55 $rbacadmin = $DIC->rbac()->admin(); 56 $ilLog = $DIC["ilLog"]; 57 $tree = $DIC->repositoryTree(); 58 $lng = $DIC->language(); 59 $ilSetting = $DIC->settings(); 60 $user = $DIC->user(); 61 62 $log = $ilLog; 63 64 include_once("./Services/Repository/exceptions/class.ilRepositoryException.php"); 65 66 // Remove duplicate ids from array 67 $a_ids = array_unique((array) $a_ids); 68 69 // FOR ALL SELECTED OBJECTS 70 $not_deletable = []; 71 foreach ($a_ids as $id) { 72 if ($tree->isDeleted($id)) { 73 $log->write(__METHOD__ . ': Object with ref_id: ' . $id . ' already deleted.'); 74 throw new ilRepositoryException($lng->txt("msg_obj_already_deleted")); 75 } 76 77 // GET COMPLETE NODE_DATA OF ALL SUBTREE NODES 78 $node_data = $tree->getNodeData($id); 79 $subtree_nodes = $tree->getSubTree($node_data); 80 81 $all_node_data[] = $node_data; 82 $all_subtree_nodes[] = $subtree_nodes; 83 84 // CHECK DELETE PERMISSION OF ALL OBJECTS 85 foreach ($subtree_nodes as $node) { 86 if ($node['type'] == 'rolf') { 87 continue; 88 } 89 if (!$rbacsystem->checkAccess('delete', $node["child"])) { 90 $not_deletable[] = $node["child"]; 91 $perform_delete = false; 92 } 93 } 94 } 95 96 // IF THERE IS ANY OBJECT WITH NO PERMISSION TO DELETE 97 if (is_array($not_deletable) && count($not_deletable) > 0) { 98 $not_deletable_titles = array(); 99 foreach ($not_deletable as $key => $ref_id) { 100 $obj_id = ilObject::_lookupObjId($ref_id); 101 $not_deletable_titles[] = ilObject::_lookupTitle($obj_id); 102 } 103 104 ilSession::clear("saved_post"); 105 throw new ilRepositoryException( 106 $lng->txt("msg_no_perm_delete") . " " . implode(', ', $not_deletable_titles) . "<br/>" . $lng->txt("msg_cancel") 107 ); 108 } 109 110 // DELETE THEM 111 if (!$all_node_data[0]["type"]) { 112 // alex: this branch looks suspicious to me... I deactivate it for 113 // now. Objects that aren't in the tree should overwrite this method. 114 throw new ilRepositoryException($lng->txt("ilRepUtil::deleteObjects: Type information missing.")); 115 } else { 116 // SAVE SUBTREE AND DELETE SUBTREE FROM TREE 117 $affected_ids = array(); 118 $affected_parents = array(); 119 foreach ($a_ids as $id) { 120 if ($tree->isDeleted($id)) { 121 $log->write(__METHOD__ . ': Object with ref_id: ' . $id . ' already deleted.'); 122 throw new ilRepositoryException($lng->txt("msg_obj_already_deleted")); 123 } 124 125 // DELETE OLD PERMISSION ENTRIES 126 $subnodes = $tree->getSubtree($tree->getNodeData($id)); 127 128 foreach ($subnodes as $subnode) { 129 $rbacadmin->revokePermission($subnode["child"]); 130 131 $affected_ids[$subnode["child"]] = $subnode["child"]; 132 $affected_parents[$subnode["child"]] = $subnode["parent"]; 133 } 134 135 // TODO: needs other handling 136 // This class shouldn't have to know anything about ECS 137 include_once('./Services/WebServices/ECS/classes/class.ilECSObjectSettings.php'); 138 ilECSObjectSettings::_handleDelete($subnodes); 139 if (!$tree->moveToTrash($id, true, $user->getId())) { 140 $log->write(__METHOD__ . ': Object with ref_id: ' . $id . ' already deleted.'); 141 throw new ilRepositoryException($lng->txt("msg_obj_already_deleted")); 142 } 143 144 // write log entry 145 $log->write("ilObjectGUI::confirmedDeleteObject(), moved ref_id " . $id . 146 " to trash"); 147 148 $affected_ids[$id] = $id; 149 } 150 151 // send global events 152 foreach ($affected_ids as $aid) { 153 $ilAppEventHandler->raise( 154 "Services/Object", 155 "toTrash", 156 array( "obj_id" => ilObject::_lookupObjId($aid), 157 "ref_id" => $aid, 158 "old_parent_ref_id" => $affected_parents[$aid] 159 ) 160 ); 161 } 162 } 163 164 if (!$ilSetting->get('enable_trash')) { 165 ilRepUtil::removeObjectsFromSystem($a_ids); 166 } 167 } 168 169 /** 170 * remove objects from trash bin and all entries therefore every object needs a specific deleteObject() method 171 * 172 * @access public 173 * @throws \ilRepositoryException 174 */ 175 public static function removeObjectsFromSystem($a_ref_ids, $a_from_recovery_folder = false) 176 { 177 global $DIC; 178 179 $logger = $DIC->logger()->rep(); 180 181 $ilLog = $DIC["ilLog"]; 182 $ilAppEventHandler = $DIC["ilAppEventHandler"]; 183 $tree = $DIC->repositoryTree(); 184 185 $log = $ilLog; 186 187 $affected_ids = array(); 188 189 // DELETE THEM 190 foreach ($a_ref_ids as $id) { 191 // GET COMPLETE NODE_DATA OF ALL SUBTREE NODES 192 if (!$a_from_recovery_folder) { 193 $trees = \ilTree::lookupTreesForNode($id); 194 $tree_id = end($trees); 195 196 if ($tree_id) { 197 $saved_tree = new \ilTree((int) $tree_id); 198 $node_data = $saved_tree->getNodeData($id); 199 $subtree_nodes = $saved_tree->getSubTree($node_data); 200 } else { 201 throw new \ilRepositoryException('No valid tree id found for node id: ' . $id); 202 } 203 } else { 204 $node_data = $tree->getNodeData($id); 205 $subtree_nodes = $tree->getSubTree($node_data); 206 } 207 208 global $DIC; 209 210 $ilUser = $DIC->user(); 211 $tree = $DIC->repositoryTree(); 212 $parent_data = $tree->getParentNodeData($node_data['ref_id']); 213 ilChangeEvent::_recordWriteEvent( 214 $node_data['obj_id'], 215 $ilUser->getId(), 216 'purge', 217 $parent_data['obj_id'] 218 ); 219 // END ChangeEvent: Record remove from system. 220 221 // remember already checked deleted node_ids 222 if (!$a_from_recovery_folder) { 223 $checked[] = -(int) $id; 224 } else { 225 $checked[] = $id; 226 } 227 228 // dive in recursive manner in each already deleted subtrees and remove these objects too 229 ilRepUtil::removeDeletedNodes($id, $checked, true, $affected_ids); 230 231 foreach ($subtree_nodes as $node) { 232 if (!$node_obj = ilObjectFactory::getInstanceByRefId($node["ref_id"], false)) { 233 continue; 234 } 235 236 // write log entry 237 $log->write("ilObjectGUI::removeFromSystemObject(), delete obj_id: " . $node_obj->getId() . 238 ", ref_id: " . $node_obj->getRefId() . ", type: " . $node_obj->getType() . ", " . 239 "title: " . $node_obj->getTitle()); 240 $affected_ids[$node["ref_id"]] = array( 241 "ref_id" => $node["ref_id"], 242 "obj_id" => $node_obj->getId(), 243 "type" => $node_obj->getType(), 244 "old_parent_ref_id" => $node["parent"]); 245 246 // this is due to bug #1860 (even if this will not completely fix it) 247 // and the fact, that media pool folders may find their way into 248 // the recovery folder (what results in broken pools, if the are deleted) 249 // Alex, 2006-07-21 250 if (!$a_from_recovery_folder || $node_obj->getType() != "fold") { 251 $node_obj->delete(); 252 } 253 } 254 255 // Use the saved tree object here (negative tree_id) 256 if (!$a_from_recovery_folder) { 257 $saved_tree->deleteTree($node_data); 258 } else { 259 $tree->deleteTree($node_data); 260 } 261 262 // write log entry 263 $log->write("ilObjectGUI::removeFromSystemObject(), deleted tree, tree_id: " . $node_data["tree"] . 264 ", child: " . $node_data["child"]); 265 } 266 267 // send global events 268 foreach ($affected_ids as $aid) { 269 $ilAppEventHandler->raise( 270 "Services/Object", 271 "delete", 272 array("obj_id" => $aid["obj_id"], 273 "ref_id" => $aid["ref_id"], 274 "type" => $aid["type"], 275 "old_parent_ref_id" => $aid["old_parent_ref_id"]) 276 ); 277 } 278 } 279 280 /** 281 * Remove already deleted objects within the objects in trash 282 */ 283 private static function removeDeletedNodes( 284 $a_node_id, 285 $a_checked, 286 $a_delete_objects, 287 &$a_affected_ids 288 ) { 289 global $DIC; 290 291 $ilLog = $DIC["ilLog"]; 292 $ilDB = $DIC->database(); 293 $tree = $DIC->repositoryTree(); 294 295 $log = $ilLog; 296 297 $q = "SELECT tree FROM tree WHERE parent= " . 298 $ilDB->quote($a_node_id, "integer") . " AND tree < 0"; 299 300 $r = $ilDB->query($q); 301 302 while ($row = $ilDB->fetchObject($r)) { 303 // only continue recursion if fetched node wasn't touched already! 304 if (!in_array($row->tree, $a_checked)) { 305 $deleted_tree = new ilTree($row->tree); 306 $a_checked[] = $row->tree; 307 308 $row->tree = $row->tree * (-1); 309 $del_node_data = $deleted_tree->getNodeData($row->tree); 310 $del_subtree_nodes = $deleted_tree->getSubTree($del_node_data); 311 312 ilRepUtil::removeDeletedNodes($row->tree, $a_checked, $a_delete_objects, $a_affected_ids); 313 314 if ($a_delete_objects) { 315 foreach ($del_subtree_nodes as $node) { 316 $node_obj = ilObjectFactory::getInstanceByRefId($node["ref_id"]); 317 318 // write log entry 319 $log->write("ilObjectGUI::removeDeletedNodes(), delete obj_id: " . $node_obj->getId() . 320 ", ref_id: " . $node_obj->getRefId() . ", type: " . $node_obj->getType() . ", " . 321 "title: " . $node_obj->getTitle()); 322 $a_affected_ids[$node["ref_id"]] = array( 323 "ref_id" => $node["ref_id"], 324 "obj_id" => $node_obj->getId(), 325 "type" => $node_obj->getType(), 326 "old_parent_ref_id" => $node["parent"]); 327 328 $node_obj->delete(); 329 } 330 } 331 332 $tree->deleteTree($del_node_data); 333 334 // write log entry 335 $log->write("ilObjectGUI::removeDeletedNodes(), deleted tree, tree_id: " . $del_node_data["tree"] . 336 ", child: " . $del_node_data["child"]); 337 } 338 } 339 340 return true; 341 } 342 343 /** 344 * Move objects from trash back to repository 345 * 346 * @param int $a_cur_ref_id 347 * @param int[] $a_ref_ids 348 * @throws \ilDatabaseException 349 * @throws \ilObjectNotFoundException 350 * @throws \ilRepositoryException 351 */ 352 public static function restoreObjects($a_cur_ref_id, $a_ref_ids) 353 { 354 global $DIC; 355 356 $rbacsystem = $DIC->rbac()->system(); 357 $ilAppEventHandler = $DIC["ilAppEventHandler"]; 358 $lng = $DIC->language(); 359 $tree = $DIC->repositoryTree(); 360 361 $cur_obj_id = ilObject::_lookupObjId($a_cur_ref_id); 362 363 $no_create = []; 364 365 foreach ($a_ref_ids as $id) { 366 $obj_data = ilObjectFactory::getInstanceByRefId($id); 367 368 if (!$rbacsystem->checkAccess('create', $a_cur_ref_id, $obj_data->getType())) { 369 $no_create[] = ilObject::_lookupTitle(ilObject::_lookupObjId($id)); 370 } 371 } 372 373 if (count($no_create)) { 374 include_once("./Services/Repository/exceptions/class.ilRepositoryException.php"); 375 throw new ilRepositoryException($lng->txt("msg_no_perm_paste") . " " . implode(',', $no_create)); 376 } 377 378 $affected_ids = array(); 379 380 foreach ($a_ref_ids as $id) { 381 $affected_ids[$id] = $id; 382 383 // INSERT AND SET PERMISSIONS 384 try { 385 $tree_ids = \ilTree::lookupTreesForNode($id); 386 $tree_id = $tree_ids[0]; 387 ilRepUtil::insertSavedNodes($id, $a_cur_ref_id, $tree_id, $affected_ids); 388 } catch (Exception $e) { 389 include_once("./Services/Repository/exceptions/class.ilRepositoryException.php"); 390 throw new ilRepositoryException('Restore from trash failed with message: ' . $e->getMessage()); 391 } 392 393 394 // BEGIN ChangeEvent: Record undelete. 395 require_once('Services/Tracking/classes/class.ilChangeEvent.php'); 396 global $DIC; 397 398 $ilUser = $DIC->user(); 399 400 401 ilChangeEvent::_recordWriteEvent( 402 ilObject::_lookupObjId($id), 403 $ilUser->getId(), 404 'undelete', 405 ilObject::_lookupObjId($tree->getParentId($id)) 406 ); 407 ilChangeEvent::_catchupWriteEvents( 408 $cur_obj_id, 409 $ilUser->getId() 410 ); 411 // END PATCH ChangeEvent: Record undelete. 412 } 413 414 // send events 415 foreach ($affected_ids as $id) { 416 // send global event 417 $ilAppEventHandler->raise( 418 "Services/Object", 419 "undelete", 420 array("obj_id" => ilObject::_lookupObjId($id), "ref_id" => $id) 421 ); 422 } 423 } 424 425 /** 426 * Recursive method to insert all saved nodes of the clipboard 427 */ 428 private static function insertSavedNodes($a_source_id, $a_dest_id, $a_tree_id, &$a_affected_ids) 429 { 430 global $DIC; 431 432 $tree = $DIC->repositoryTree(); 433 434 ilLoggerFactory::getLogger('rep')->debug('Restoring from trash: source_id: ' . $a_source_id . ', dest_id: ' . $a_dest_id . ', tree_id:' . $a_tree_id); 435 ilLoggerFactory::getLogger('rep')->info('Restoring ref_id ' . $a_source_id . ' from trash.'); 436 437 // read child of node 438 $saved_tree = new ilTree($a_tree_id); 439 $childs = $saved_tree->getChilds($a_source_id); 440 441 // then delete node and put in tree 442 try { 443 $tree->insertNodeFromTrash($a_source_id, $a_dest_id, $a_tree_id, IL_LAST_NODE, true); 444 } catch (Exception $e) { 445 ilLoggerFactory::getLogger('rep')->error('Restore from trash failed with message: ' . $e->getMessage()); 446 throw $e; 447 } 448 449 include_once './Services/Object/classes/class.ilObjectFactory.php'; 450 $factory = new ilObjectFactory(); 451 $ref_obj = $factory->getInstanceByRefId($a_source_id, false); 452 if ($ref_obj instanceof ilObject) { 453 $lroles = $GLOBALS['rbacreview']->getRolesOfRoleFolder($a_source_id, true); 454 foreach ($lroles as $role_id) { 455 include_once './Services/AccessControl/classes/class.ilObjRole.php'; 456 $role = new ilObjRole($role_id); 457 $role->setParent($a_source_id); 458 $role->delete(); 459 } 460 if ($a_dest_id) { 461 $ref_obj->setPermissions($a_dest_id); 462 } 463 } 464 foreach ($childs as $child) { 465 ilRepUtil::insertSavedNodes($child["child"], $a_source_id, $a_tree_id, $a_affected_ids); 466 } 467 } 468 469 470 471 // 472 // OBJECT TYPE HANDLING / REMOVAL 473 // 474 475 protected function findTypeInTrash($a_type) 476 { 477 $ilDB = $this->db; 478 479 $res = array(); 480 481 $set = $ilDB->query("SELECT child" . 482 " FROM tree" . 483 " JOIN object_reference ref ON (tree.child = ref.ref_id)" . 484 " JOIN object_data od ON (od.obj_id = ref.obj_id)" . 485 " WHERE tree.tree < " . $ilDB->quote(0, "integer") . 486 " AND od.type = " . $ilDB->quote($a_type, "text")); 487 while ($row = $ilDB->fetchAssoc($set)) { 488 $res[] = $row["child"]; 489 } 490 491 return $res; 492 } 493 494 protected function getObjectTypeId($a_type) 495 { 496 $ilDB = $this->db; 497 498 $set = $ilDB->query("SELECT obj_id" . 499 " FROM object_data " . 500 " WHERE type = " . $ilDB->quote("typ", "text") . 501 " AND title = " . $ilDB->quote($a_type, "text")); 502 $row = $ilDB->fetchAssoc($set); 503 return $row["obj_id"]; 504 } 505 506 public function deleteObjectType($a_type) 507 { 508 $ilDB = $this->db; 509 $tree = $this->tree; 510 $ilSetting = $this->settings; 511 512 // delete object instances (repository/trash) 513 514 $ref_ids_in_tree = $tree->getSubTree($tree->getNodeData(ROOT_FOLDER_ID), false, $a_type); 515 if ($ref_ids_in_tree) { 516 $this->deleteObjects(null, $ref_ids_in_tree); 517 } 518 519 if ($ilSetting->get('enable_trash')) { 520 $ref_ids_in_trash = $this->findTypeInTrash($a_type); 521 if ($ref_ids_in_trash) { 522 self::removeObjectsFromSystem($ref_ids_in_trash); 523 } 524 } 525 526 // delete "component" 527 $type_id = $this->getObjectTypeId($a_type); 528 if ($type_id) { 529 // see ilRepositoryObjectPlugin::beforeActivation() 530 531 $ilDB->manipulate("DELETE FROM object_data" . 532 " WHERE obj_id = " . $ilDB->quote($type_id, "integer")); 533 534 // RBAC 535 536 // basic operations 537 $ilDB->manipulate("DELETE FROM rbac_ta" . 538 " WHERE typ_id = " . $ilDB->quote($type_id, "integer") /*. 539 " AND ".$ilDB->in("ops_id", array(1, 2, 3, 4, 6), "", "integer") */); 540 541 // creation operation 542 $set = $ilDB->query("SELECT ops_id" . 543 " FROM rbac_operations " . 544 " WHERE class = " . $ilDB->quote("create", "text") . 545 " AND operation = " . $ilDB->quote("create_" . $a_type, "text")); 546 $row = $ilDB->fetchAssoc($set); 547 $create_ops_id = $row["ops_id"]; 548 if ($create_ops_id) { 549 $ilDB->manipulate("DELETE FROM rbac_operations" . 550 " WHERE ops_id = " . $ilDB->quote($create_ops_id, "integer")); 551 552 $ilDB->manipulate("DELETE FROM rbac_templates" . 553 " WHERE ops_id = " . $ilDB->quote($create_ops_id, "integer")); 554 555 // container create 556 foreach (array("root", "cat", "crs", "grp", "fold") as $parent_type) { 557 $parent_type_id = $this->getObjectTypeId($parent_type); 558 if ($parent_type_id) { 559 $ilDB->manipulate("DELETE FROM rbac_ta" . 560 " WHERE typ_id = " . $ilDB->quote($parent_type_id, "integer") . 561 " AND ops_id = " . $ilDB->quote($create_ops_id, "integer")); 562 } 563 } 564 } 565 } 566 567 // delete new item settings 568 include_once "Services/Repository/classes/class.ilObjRepositorySettings.php"; 569 ilObjRepositorySettings::deleteObjectType($a_type); 570 } 571} 572