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