Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
62.28% covered (warning)
62.28%
502 / 806
52.54% covered (warning)
52.54%
31 / 59
CRAP
0.00% covered (danger)
0.00%
0 / 1
SeedDMS_Core_Folder
62.28% covered (warning)
62.28%
502 / 806
52.54% covered (warning)
52.54%
31 / 59
8766.63
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 clearCache
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 isType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSearchFields
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 getSearchTables
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getInstanceByData
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getInstance
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 getInstanceByName
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
8.02
 applyDecorators
33.33% covered (danger)
33.33%
2 / 6
0.00% covered (danger)
0.00%
0 / 1
5.67
 getName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setName
52.94% covered (warning)
52.94%
9 / 17
0.00% covered (danger)
0.00%
0 / 1
14.67
 getComment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setComment
52.94% covered (warning)
52.94%
9 / 17
0.00% covered (danger)
0.00%
0 / 1
14.67
 getDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDate
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 getParent
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 isSubFolder
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 setParent
85.00% covered (warning)
85.00%
34 / 40
0.00% covered (danger)
0.00%
0 / 1
15.76
 getOwner
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setOwner
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 getDefaultAccess
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 setDefaultAccess
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 inheritsAccess
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setInheritAccess
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 getSequence
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setSequence
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 hasSubFolders
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
4.03
 hasSubFolderByName
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 getSubFolders
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
16
 addSubFolder
58.62% covered (warning)
58.62%
17 / 29
0.00% covered (danger)
0.00%
0 / 1
22.20
 getPath
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
7
 getFolderPathPlain
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
5.05
 isDescendant
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 hasDocuments
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 hasDocumentByName
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 getDocuments
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
17
 countChildren
93.18% covered (success)
93.18%
41 / 44
0.00% covered (danger)
0.00%
0 / 1
15.07
 addDocument
50.00% covered (danger)
50.00%
20 / 40
0.00% covered (danger)
0.00%
0 / 1
53.12
 removeFromDatabase
50.00% covered (danger)
50.00%
16 / 32
0.00% covered (danger)
0.00%
0 / 1
30.00
 remove
74.07% covered (warning)
74.07%
20 / 27
0.00% covered (danger)
0.00%
0 / 1
26.97
 emptyFolder
86.36% covered (warning)
86.36%
19 / 22
0.00% covered (danger)
0.00%
0 / 1
14.50
 getAccessList
90.91% covered (success)
90.91%
20 / 22
0.00% covered (danger)
0.00%
0 / 1
12.11
 clearAccessList
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
3.02
 addAccess
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
8.01
 changeAccess
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
42
 removeAccess
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
 getAccessMode
70.00% covered (warning)
70.00%
21 / 30
0.00% covered (danger)
0.00%
0 / 1
32.91
 getGroupAccessMode
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
56
 getNotifyList
50.00% covered (danger)
50.00%
8 / 16
0.00% covered (danger)
0.00%
0 / 1
22.50
 cleanNotifyList
60.00% covered (warning)
60.00%
6 / 10
0.00% covered (danger)
0.00%
0 / 1
8.30
 addNotify
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
306
 removeNotify
55.00% covered (warning)
55.00%
11 / 20
0.00% covered (danger)
0.00%
0 / 1
13.83
 getApproversList
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getReadAccessList
0.00% covered (danger)
0.00%
0 / 69
0.00% covered (danger)
0.00%
0 / 1
992
 getFolderList
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 repair
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
 getDocumentsMinMax
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 getFoldersMinMax
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 reorderDocuments
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2/**
3 * Implementation of a folder in the document management system
4 *
5 * @category   DMS
6 * @package    SeedDMS_Core
7 * @license    GPL2
8 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
9 *             Uwe Steinmann <uwe@steinmann.cx>
10 * @copyright  Copyright (C) 2002-2005 Markus Westphal, 2006-2008 Malcolm Cowe,
11 *             2010 Matteo Lucarelli, 2010 Uwe Steinmann
12 * @version    Release: @package_version@
13 */
14
15/**
16 * Class to represent a folder in the document management system
17 *
18 * A folder in SeedDMS is equivalent to a directory in a regular file
19 * system. It can contain further subfolders and documents. Each folder
20 * has a single parent except for the root folder which has no parent.
21 *
22 * @category   DMS
23 * @package    SeedDMS_Core
24 * @version    @version@
25 * @author     Uwe Steinmann <uwe@steinmann.cx>
26 * @copyright  Copyright (C) 2002-2005 Markus Westphal, 2006-2008 Malcolm Cowe,
27 *             2010 Matteo Lucarelli, 2010 Uwe Steinmann
28 * @version    Release: @package_version@
29 */
30class SeedDMS_Core_Folder extends SeedDMS_Core_Object {
31    /**
32     * @var string name of folder
33     */
34    protected $_name;
35
36    /**
37     * @var integer id of parent folder
38     */
39    protected $_parentID;
40
41    /**
42     * @var string comment of document
43     */
44    protected $_comment;
45
46    /**
47     * @var integer id of user who is the owner
48     */
49    protected $_ownerID;
50
51    /**
52     * @var boolean true if access is inherited, otherwise false
53     */
54    protected $_inheritAccess;
55
56    /**
57     * @var integer default access if access rights are not inherited
58     */
59    protected $_defaultAccess;
60
61    /**
62     * @var array list of notifications for users and groups
63     */
64    protected $_readAccessList;
65
66    /**
67     * @var array list of notifications for users and groups
68     */
69    public $_notifyList;
70
71    /**
72     * @var integer position of folder within the parent folder
73     */
74    protected $_sequence;
75
76    /**
77     * @var
78     */
79    protected $_date;
80
81    /**
82     * @var SeedDMS_Core_Folder cached parent folder
83     */
84    protected $_parent;
85
86    /**
87     * @var SeedDMS_Core_User cached owner of folder
88     */
89    protected $_owner;
90
91    /**
92     * @var SeedDMS_Core_Folder[] cached array of sub folders
93     */
94    protected $_subFolders;
95
96    /**
97     * @var SeedDMS_Core_Document[] cache array of child documents
98     */
99    protected $_documents;
100
101    /**
102     * @var SeedDMS_Core_UserAccess[]|SeedDMS_Core_GroupAccess[]
103     */
104    protected $_accessList;
105
106    /**
107     * SeedDMS_Core_Folder constructor.
108     * @param $id
109     * @param $name
110     * @param $parentID
111     * @param $comment
112     * @param $date
113     * @param $ownerID
114     * @param $inheritAccess
115     * @param $defaultAccess
116     * @param $sequence
117     */
118    function __construct($id, $name, $parentID, $comment, $date, $ownerID, $inheritAccess, $defaultAccess, $sequence) { /* {{{ */
119        parent::__construct($id);
120        $this->_id = $id;
121        $this->_name = $name;
122        $this->_parentID = $parentID;
123        $this->_comment = $comment;
124        $this->_date = $date;
125        $this->_ownerID = $ownerID;
126        $this->_inheritAccess = $inheritAccess;
127        $this->_defaultAccess = $defaultAccess;
128        $this->_sequence = $sequence;
129        $this->_notifyList = array();
130        /* Cache */
131        $this->clearCache();
132    } /* }}} */
133
134    /**
135     * Clear cache of this instance.
136     *
137     * The result of some expensive database actions (e.g. get all subfolders
138     * or documents) will be saved in a class variable to speed up consecutive
139     * calls of the same method. If a second call of the same method shall not
140     * use the cache, then it must be cleared.
141     *
142     */
143    public function clearCache() { /* {{{ */
144        $this->_parent = null;
145        $this->_owner = null;
146        $this->_subFolders = null;
147        $this->_documents = null;
148        $this->_accessList = null;
149        $this->_notifyList = null;
150    } /* }}} */
151
152    /**
153     * Check if this object is of type 'folder'.
154     *
155     * @param string $type type of object
156     */
157    public function isType($type) { /* {{{ */
158        return $type == 'folder';
159    } /* }}} */
160
161    /**
162     * Return an array of database fields which used for searching
163     * a term entered in the database search form
164     *
165     * @param SeedDMS_Core_DMS $dms
166     * @param array $searchin integer list of search scopes (2=name, 3=comment,
167     * 4=attributes)
168     * @return array list of database fields
169     */
170    public static function getSearchFields($dms, $searchin) { /* {{{ */
171        $db = $dms->getDB();
172
173        $searchFields = array();
174        if (in_array(2, $searchin)) {
175            $searchFields[] = "`tblFolders`.`name`";
176        }
177        if (in_array(3, $searchin)) {
178            $searchFields[] = "`tblFolders`.`comment`";
179        }
180        if (in_array(4, $searchin)) {
181            $searchFields[] = "`tblFolderAttributes`.`value`";
182        }
183        if (in_array(5, $searchin)) {
184            $searchFields[] = $db->castToText("`tblFolders`.`id`");
185        }
186        return $searchFields;
187    } /* }}} */
188
189    /**
190     * Return a sql statement with all tables used for searching.
191     * This must be a syntactically correct left join of all tables.
192     *
193     * @return string sql expression for left joining tables
194     */
195    public static function getSearchTables() { /* {{{ */
196        $sql = "`tblFolders` LEFT JOIN `tblFolderAttributes` on `tblFolders`.`id`=`tblFolderAttributes`.`folder`";
197        return $sql;
198    } /* }}} */
199
200    /**
201     * Return a folder by its database record
202     *
203     * @param array $resArr array of folder data as returned by database
204     * @param SeedDMS_Core_DMS $dms
205     * @return SeedDMS_Core_Folder|bool instance of SeedDMS_Core_Folder if document exists
206     */
207    public static function getInstanceByData($resArr, $dms) { /* {{{ */
208        $classname = $dms->getClassname('folder');
209        /** @var SeedDMS_Core_Folder $folder */
210        $folder = new $classname($resArr["id"], $resArr["name"], $resArr["parent"], $resArr["comment"], $resArr["date"], $resArr["owner"], $resArr["inheritAccess"], $resArr["defaultAccess"], $resArr["sequence"]);
211        $folder->setDMS($dms);
212        $folder = $folder->applyDecorators();
213        return $folder;
214    } /* }}} */
215
216    /**
217     * Return a folder by its id
218     *
219     * @param integer $id id of folder
220     * @param SeedDMS_Core_DMS $dms
221     * @return SeedDMS_Core_Folder|bool instance of SeedDMS_Core_Folder if document exists, null
222     * if document does not exist, false in case of error
223     */
224    public static function getInstance($id, $dms) { /* {{{ */
225        $db = $dms->getDB();
226
227        $queryStr = "SELECT * FROM `tblFolders` WHERE `id` = " . (int) $id;
228        if($dms->checkWithinRootDir && ($id != $dms->rootFolderID))
229            $queryStr .= " AND `folderList` LIKE '%:".$dms->rootFolderID.":%'";
230        $resArr = $db->getResultArray($queryStr);
231        if (is_bool($resArr) && $resArr == false)
232            return false;
233        elseif (count($resArr) != 1)
234            return null;
235
236        return self::getInstanceByData($resArr[0], $dms);
237    } /* }}} */
238
239    /**
240     * Return a folder by its name
241     *
242     * This function retrieves a folder from the database by its name. The
243     * search covers the whole database. If
244     * the parameter $folder is not null, it will search for the name
245     * only within this parent folder. It will not be done recursively.
246     *
247     * @param string $name name of the folder
248     * @param SeedDMS_Core_Folder $folder parent folder
249     * @return SeedDMS_Core_Folder|boolean found folder or false
250     */
251    public static function getInstanceByName($name, $folder, $dms) { /* {{{ */
252        if (!$name) return false;
253
254        $db = $dms->getDB();
255        $queryStr = "SELECT * FROM `tblFolders` WHERE `name` = " . $db->qstr($name);
256        if($folder)
257            $queryStr .= " AND `parent` = ". $folder->getID();
258        if($dms->checkWithinRootDir && ($id != $dms->rootFolderID))
259            $queryStr .= " AND `folderList` LIKE '%:".$dms->rootFolderID.":%'";
260        $queryStr .= " LIMIT 1";
261        $resArr = $db->getResultArray($queryStr);
262
263        if (is_bool($resArr) && $resArr == false)
264            return false;
265
266        if(!$resArr)
267            return null;
268
269        return self::getInstanceByData($resArr[0], $dms);
270    } /* }}} */
271
272    /**
273     * Apply decorators
274     *
275     * @return object final object after all decorators has been applied
276     */
277    function applyDecorators() { /* {{{ */
278        if($decorators = $this->_dms->getDecorators('folder')) {
279            $s = $this;
280            foreach($decorators as $decorator) {
281                $s = new $decorator($s);
282            }
283            return $s;
284        } else {
285            return $this;
286        }
287    } /* }}} */
288
289    /**
290     * Get the name of the folder.
291     *
292     * @return string name of folder
293     */
294    public function getName() { return $this->_name; }
295
296    /**
297     * Set the name of the folder.
298     *
299     * @param string $newName set a new name of the folder
300     * @return bool
301     */
302    public function setName($newName) { /* {{{ */
303        $db = $this->_dms->getDB();
304
305        /* Check if 'onPreSetName' callback is set */
306        if(isset($this->_dms->callbacks['onPreSetName'])) {
307            foreach($this->_dms->callbacks['onPreSetName'] as $callback) {
308                $ret = call_user_func($callback[0], $callback[1], $this, $newName);
309                if(is_bool($ret))
310                    return $ret;
311            }
312        }
313
314        $queryStr = "UPDATE `tblFolders` SET `name` = " . $db->qstr($newName) . " WHERE `id` = ". $this->_id;
315        if (!$db->getResult($queryStr))
316            return false;
317
318        $oldName = $this->_name;
319        $this->_name = $newName;
320
321        /* Check if 'onPostSetName' callback is set */
322        if(isset($this->_dms->callbacks['onPostSetName'])) {
323            foreach($this->_dms->callbacks['onPostSetName'] as $callback) {
324                $ret = call_user_func($callback[0], $callback[1], $this, $oldName);
325                if(is_bool($ret))
326                    return $ret;
327            }
328        }
329
330        return true;
331    } /* }}} */
332
333    /**
334     * @return string
335     */
336    public function getComment() { return $this->_comment; }
337
338    /**
339     * @param $newComment
340     * @return bool
341     */
342    public function setComment($newComment) { /* {{{ */
343        $db = $this->_dms->getDB();
344
345        /* Check if 'onPreSetComment' callback is set */
346        if(isset($this->_dms->callbacks['onPreSetComment'])) {
347            foreach($this->_dms->callbacks['onPreSetComment'] as $callback) {
348                $ret = call_user_func($callback[0], $callback[1], $this, $newComment);
349                if(is_bool($ret))
350                    return $ret;
351            }
352        }
353
354        $queryStr = "UPDATE `tblFolders` SET `comment` = " . $db->qstr($newComment) . " WHERE `id` = ". $this->_id;
355        if (!$db->getResult($queryStr))
356            return false;
357
358        $oldComment = $this->_comment;
359        $this->_comment = $newComment;
360
361        /* Check if 'onPostSetComment' callback is set */
362        if(isset($this->_dms->callbacks['onPostSetComment'])) {
363            foreach($this->_dms->callbacks['onPostSetComment'] as $callback) {
364                $ret = call_user_func($callback[0], $callback[1], $this, $oldComment);
365                if(is_bool($ret))
366                    return $ret;
367            }
368        }
369
370        return true;
371    } /* }}} */
372
373    /**
374     * Return creation date of folder
375     *
376     * @return integer unix timestamp of creation date
377     */
378    public function getDate() { /* {{{ */
379        return $this->_date;
380    } /* }}} */
381
382    /**
383     * Set creation date of the folder
384     *
385     * @param integer $date timestamp of creation date. If false then set it
386     * to the current timestamp
387     * @return boolean true on success
388     */
389    function setDate($date) { /* {{{ */
390        $db = $this->_dms->getDB();
391
392        if($date === false)
393            $date = time();
394        else {
395            if(!is_numeric($date))
396                return false;
397        }
398
399        $queryStr = "UPDATE `tblFolders` SET `date` = " . (int) $date . " WHERE `id` = ". $this->_id;
400        if (!$db->getResult($queryStr))
401            return false;
402        $this->_date = $date;
403        return true;
404    } /* }}} */
405
406    /**
407     * Returns the parent
408     *
409     * @return null|bool|SeedDMS_Core_Folder returns null, if there is no parent folder
410     * and false in case of an error
411     */
412    public function getParent() { /* {{{ */
413        if ($this->_id == $this->_dms->rootFolderID || empty($this->_parentID)) {
414            return null;
415        }
416
417        if (!isset($this->_parent)) {
418            $this->_parent = $this->_dms->getFolder($this->_parentID);
419        }
420        return $this->_parent;
421    } /* }}} */
422
423    /**
424     * Check if the folder is subfolder
425     *
426     * This method checks if the current folder is in the path of the 
427     * passed subfolder. In that case the current folder is a parent,
428     * grant parent, grant grant parent, etc. of the subfolder or
429     * to say it differently the passed folder is somewhere below the
430     * current folder.
431     *
432     * This is basically the opposite of {@see SeedDMS_Core_Folder::isDescendant()}
433     *
434     * @param SeedDMS_Core_Folder $subfolder folder to be checked if it is
435     * a subfolder on any level of the current folder
436     * @return bool true if passed folder is a subfolder, otherwise false
437     */
438    function isSubFolder($subfolder) { /* {{{ */
439        $target_path = $subfolder->getPath();
440        foreach($target_path as $next_folder) {
441            // the target folder contains this instance in the parent path
442            if($this->getID() == $next_folder->getID()) return true;
443        }
444        return false;
445    } /* }}} */
446
447    /**
448     * Set a new folder
449     *
450     * This function moves a folder from one parent folder into another parent
451     * folder. It will fail if the root folder is moved.
452     *
453     * @param SeedDMS_Core_Folder $newParent new parent folder
454     * @return boolean true if operation was successful otherwise false
455     */
456    public function setParent($newParent) { /* {{{ */
457        $db = $this->_dms->getDB();
458
459        if ($this->_id == $this->_dms->rootFolderID || empty($this->_parentID)) {
460            return false;
461        }
462
463        /* Check if the new parent is the folder to be moved or even
464         * a subfolder of that folder
465         */
466        if($this->isSubFolder($newParent)) {
467            return false;
468        }
469
470        // Update the folderList of the folder
471        $pathPrefix="";
472        $path = $newParent->getPath();
473        foreach ($path as $f) {
474            $pathPrefix .= ":".$f->getID();
475        }
476        if (strlen($pathPrefix)>1) {
477            $pathPrefix .= ":";
478        }
479        $queryStr = "UPDATE `tblFolders` SET `parent` = ".$newParent->getID().", `folderList`='".$pathPrefix."' WHERE `id` = ". $this->_id;
480        $res = $db->getResult($queryStr);
481        if (!$res)
482            return false;
483
484        $this->_parentID = $newParent->getID();
485        $this->_parent = $newParent;
486
487        // Must also ensure that any documents in this folder tree have their
488        // folderLists updated.
489        $pathPrefix="";
490        $path = $this->getPath();
491        foreach ($path as $f) {
492            $pathPrefix .= ":".$f->getID();
493        }
494        if (strlen($pathPrefix)>1) {
495            $pathPrefix .= ":";
496        }
497
498        /* Update path in folderList for all documents */
499        $queryStr = "SELECT `tblDocuments`.`id`, `tblDocuments`.`folderList` FROM `tblDocuments` WHERE `folderList` LIKE '%:".$this->_id.":%'";
500        $resArr = $db->getResultArray($queryStr);
501        if (is_bool($resArr) && $resArr == false)
502            return false;
503
504        foreach ($resArr as $row) {
505            $newPath = preg_replace("/^.*:".$this->_id.":(.*$)/", $pathPrefix."\\1", $row["folderList"]);
506            $queryStr="UPDATE `tblDocuments` SET `folderList` = '".$newPath."' WHERE `tblDocuments`.`id` = '".$row["id"]."'";
507            /** @noinspection PhpUnusedLocalVariableInspection */
508            $res = $db->getResult($queryStr);
509        }
510
511        /* Update path in folderList for all folders */
512        $queryStr = "SELECT `tblFolders`.`id`, `tblFolders`.`folderList` FROM `tblFolders` WHERE `folderList` LIKE '%:".$this->_id.":%'";
513        $resArr = $db->getResultArray($queryStr);
514        if (is_bool($resArr) && $resArr == false)
515            return false;
516
517        foreach ($resArr as $row) {
518            $newPath = preg_replace("/^.*:".$this->_id.":(.*$)/", $pathPrefix."\\1", $row["folderList"]);
519            $queryStr="UPDATE `tblFolders` SET `folderList` = '".$newPath."' WHERE `tblFolders`.`id` = '".$row["id"]."'";
520            /** @noinspection PhpUnusedLocalVariableInspection */
521            $res = $db->getResult($queryStr);
522        }
523
524        return true;
525    } /* }}} */
526
527    /**
528     * Returns the owner
529     *
530     * @return object owner of the folder
531     */
532    public function getOwner() { /* {{{ */
533        if (!isset($this->_owner))
534            $this->_owner = $this->_dms->getUser($this->_ownerID);
535        return $this->_owner;
536    } /* }}} */
537
538    /**
539     * Set the owner
540     *
541     * @param SeedDMS_Core_User $newOwner of the folder
542     * @return boolean true if successful otherwise false
543     */
544    function setOwner($newOwner) { /* {{{ */
545        $db = $this->_dms->getDB();
546
547        $queryStr = "UPDATE `tblFolders` set `owner` = " . $newOwner->getID() . " WHERE `id` = " . $this->_id;
548        if (!$db->getResult($queryStr))
549            return false;
550
551        $this->_ownerID = $newOwner->getID();
552        $this->_owner = $newOwner;
553        return true;
554    } /* }}} */
555
556    /**
557     * @return bool|int
558     */
559    function getDefaultAccess() { /* {{{ */
560        if ($this->inheritsAccess()) {
561            /* Access is supposed to be inherited but it could be that there
562             * is no parent because the configured root folder id is somewhere
563             * below the actual root folder.
564             */
565            $res = $this->getParent();
566            if ($res)
567                return $this->_parent->getDefaultAccess();
568        }
569
570        return $this->_defaultAccess;
571    } /* }}} */
572
573    /**
574     * Set default access mode
575     *
576     * This method sets the default access mode and also removes all notifiers which
577     * will not have read access anymore.
578     *
579     * @param integer $mode access mode
580     * @param boolean $noclean set to true if notifier list shall not be clean up
581     * @return bool
582     */
583    function setDefaultAccess($mode, $noclean=false) { /* {{{ */
584        $db = $this->_dms->getDB();
585
586        $queryStr = "UPDATE `tblFolders` set `defaultAccess` = " . (int) $mode . " WHERE `id` = " . $this->_id;
587        if (!$db->getResult($queryStr))
588            return false;
589
590        $this->_defaultAccess = $mode;
591
592        if(!$noclean)
593            $this->cleanNotifyList();
594
595        return true;
596    } /* }}} */
597
598    function inheritsAccess() { return $this->_inheritAccess; }
599
600    /**
601     * Set inherited access mode
602     * Setting inherited access mode will set or unset the internal flag which
603     * controls if the access mode is inherited from the parent folder or not.
604     * It will not modify the
605     * access control list for the current object. It will remove all
606     * notifications of users which do not even have read access anymore
607     * after setting or unsetting inherited access.
608     *
609     * @param boolean $inheritAccess set to true for setting and false for
610     *        unsetting inherited access mode
611     * @param boolean $noclean set to true if notifier list shall not be clean up
612     * @return boolean true if operation was successful otherwise false
613     */
614    function setInheritAccess($inheritAccess, $noclean=false) { /* {{{ */
615        $db = $this->_dms->getDB();
616
617        $inheritAccess = ($inheritAccess) ? "1" : "0";
618
619        $queryStr = "UPDATE `tblFolders` SET `inheritAccess` = " . (int) $inheritAccess . " WHERE `id` = " . $this->_id;
620        if (!$db->getResult($queryStr))
621            return false;
622
623        $this->_inheritAccess = $inheritAccess;
624
625        if(!$noclean)
626            $this->cleanNotifyList();
627
628        return true;
629    } /* }}} */
630
631    function getSequence() { return $this->_sequence; }
632
633    function setSequence($seq) { /* {{{ */
634        $db = $this->_dms->getDB();
635
636        $queryStr = "UPDATE `tblFolders` SET `sequence` = " . $seq . " WHERE `id` = " . $this->_id;
637        if (!$db->getResult($queryStr))
638            return false;
639
640        $this->_sequence = $seq;
641        return true;
642    } /* }}} */
643
644    /**
645     * Check if folder has subfolders
646     * This function just checks if a folder has subfolders disregarding
647     * any access rights.
648     *
649     * @return int number of subfolders or false in case of an error
650     */
651    function hasSubFolders() { /* {{{ */
652        $db = $this->_dms->getDB();
653        if (isset($this->_subFolders)) {
654            /** @noinspection PhpUndefinedFieldInspection */
655            return count($this->_subFolders);
656        }
657        $queryStr = "SELECT count(*) as c FROM `tblFolders` WHERE `parent` = " . $this->_id;
658        $resArr = $db->getResultArray($queryStr);
659        if (is_bool($resArr) && !$resArr)
660            return false;
661
662        return (int) $resArr[0]['c'];
663    } /* }}} */
664
665    /**
666     * Check if folder has as subfolder with given name
667     *
668     * @param string $name
669     * @return bool true if subfolder exists, false if not or in case
670     * of an error
671     */
672    function hasSubFolderByName($name) { /* {{{ */
673        $db = $this->_dms->getDB();
674        /* Always check the database instead of iterating over $this->_documents, because
675         * it is probably not slower
676         */
677        $queryStr = "SELECT count(*) as c FROM `tblFolders` WHERE `parent` = " . $this->_id . " AND `name` = ".$db->qstr($name);
678        $resArr = $db->getResultArray($queryStr);
679        if (is_bool($resArr) && !$resArr)
680            return false;
681
682        return ($resArr[0]['c'] > 0);
683    } /* }}} */
684
685    /**
686     * Returns a list of subfolders
687     * This function does not check for access rights. Use
688     * {@link SeedDMS_Core_DMS::filterAccess} for checking each folder against
689     * the currently logged in user and the access rights.
690     *
691     * @param string $orderby if set to 'n' the list is ordered by name, otherwise
692     *        it will be ordered by sequence
693     * @param string $dir direction of sorting (asc or desc)
694     * @param integer $limit limit number of subfolders
695     * @param integer $offset offset in retrieved list of subfolders
696     * @return SeedDMS_Core_Folder[]|bool list of folder objects or false in case of an error
697     */
698    function getSubFolders($orderby="", $dir="asc", $limit=0, $offset=0) { /* {{{ */
699        $db = $this->_dms->getDB();
700
701        if (!isset($this->_subFolders)) {
702            $queryStr = "SELECT * FROM `tblFolders` WHERE `parent` = " . $this->_id;
703
704            if ($orderby && $orderby[0]=="n") $queryStr .= " ORDER BY `name`";
705            elseif ($orderby && $orderby[0]=="s") $queryStr .= " ORDER BY `sequence`";
706            elseif ($orderby && $orderby[0]=="d") $queryStr .= " ORDER BY `date`";
707            if($dir == 'desc')
708                $queryStr .= " DESC";
709            if(is_int($limit) && $limit > 0) {
710                $queryStr .= " LIMIT ".$limit;
711                if(is_int($offset) && $offset > 0)
712                    $queryStr .= " OFFSET ".$offset;
713            }
714
715            $resArr = $db->getResultArray($queryStr);
716            if (is_bool($resArr) && $resArr == false)
717                return false;
718
719            $classname = $this->_dms->getClassname('folder');
720            $this->_subFolders = array();
721            for ($i = 0; $i < count($resArr); $i++)
722//                $this->_subFolders[$i] = $this->_dms->getFolder($resArr[$i]["id"]);
723                $this->_subFolders[$i] = $classname::getInstanceByData($resArr[$i], $this->_dms);
724        }
725
726        return $this->_subFolders;
727    } /* }}} */
728
729    /**
730     * Add a new subfolder
731     *
732     * @param string $name name of folder
733     * @param string $comment comment of folder
734     * @param object $owner owner of folder
735     * @param integer $sequence position of folder in list of sub folders.
736     * @param array $attributes list of document attributes. The element key
737     *        must be the id of the attribute definition.
738     * @return bool|SeedDMS_Core_Folder
739     *         an error.
740     */
741    function addSubFolder($name, $comment, $owner, $sequence, $attributes=array()) { /* {{{ */
742        $db = $this->_dms->getDB();
743
744        // Set the folderList of the folder
745        $pathPrefix="";
746        $path = $this->getPath();
747        foreach ($path as $f) {
748            $pathPrefix .= ":".$f->getID();
749        }
750        if (strlen($pathPrefix)>1) {
751            $pathPrefix .= ":";
752        }
753
754        $db->startTransaction();
755
756        //inheritAccess = true, defaultAccess = M_READ
757        $queryStr = "INSERT INTO `tblFolders` (`name`, `parent`, `folderList`, `comment`, `date`, `owner`, `inheritAccess`, `defaultAccess`, `sequence`) ".
758                    "VALUES (".$db->qstr($name).", ".$this->_id.", ".$db->qstr($pathPrefix).", ".$db->qstr($comment).", ".$db->getCurrentTimestamp().", ".$owner->getID().", 1, ".M_READ.", ". $sequence.")";
759        if (!$db->getResult($queryStr)) {
760            $db->rollbackTransaction();
761            return false;
762        }
763        $newFolder = $this->_dms->getFolder($db->getInsertID('tblFolders'));
764        unset($this->_subFolders);
765
766        if($attributes) {
767            foreach($attributes as $attrdefid=>$attribute) {
768                if($attribute)
769                    if($attrdef = $this->_dms->getAttributeDefinition($attrdefid)) {
770                        if(!$newFolder->setAttributeValue($attrdef, $attribute)) {
771                            $db->rollbackTransaction();
772                            return false;
773                        }
774                    } else {
775                        $db->rollbackTransaction();
776                        return false;
777                    }
778            }
779        }
780
781        $db->commitTransaction();
782
783        /* Check if 'onPostAddSubFolder' callback is set */
784        if(isset($this->_dms->callbacks['onPostAddSubFolder'])) {
785            foreach($this->_dms->callbacks['onPostAddSubFolder'] as $callback) {
786                    /** @noinspection PhpStatementHasEmptyBodyInspection */
787                    if(!call_user_func($callback[0], $callback[1], $newFolder)) {
788                }
789            }
790        }
791
792        return $newFolder;
793    } /* }}} */
794
795    /**
796     * Returns an array of all parents, grand parent, etc. up to root folder.
797     * The folder itself is the last element of the array.
798     *
799     * @return array|bool
800     */
801    function getPath() { /* {{{ */
802        if (!isset($this->_parentID) || ($this->_parentID == "") || ($this->_parentID == 0) || ($this->_id == $this->_dms->rootFolderID)) {
803            return array($this);
804        }
805        else {
806            $res = $this->getParent();
807            if (!$res) return false;
808
809            $path = $this->_parent->getPath();
810            if (!$path) return false;
811
812            array_push($path, $this);
813            return $path;
814        }
815    } /* }}} */
816
817    /**
818     * Returns a file system path
819     *
820     * This path contains by default spaces around the slashes for better readability.
821     * Run str_replace(' / ', '/', $path) on it or pass '/' as $sep to get a valid unix
822     * file system path.
823     *
824     * @param bool $skiproot skip the name of the root folder and start with $sep
825     * @param string $sep separator between path elements
826     * @return string path separated with ' / '
827     */
828    function getFolderPathPlain($skiproot = false, $sep = ' / ') { /* {{{ */
829        $path="".$sep;
830        $folderPath = $this->getPath();
831        for ($i = 0; $i < count($folderPath); $i++) {
832            if($i > 0 || !$skiproot) {
833                $path .= $folderPath[$i]->getName();
834                if ($i+1 < count($folderPath))
835                    $path .= $sep;
836            }
837        }
838        return trim($path);
839    } /* }}} */
840
841    /**
842     * Check, if this folder is a subfolder of a given folder
843     *
844     * This is basically the opposite of {@see SeedDMS_Core_Folder::isSubFolder()}
845     *
846     * @param object $folder parent folder
847     * @return boolean true if folder is a subfolder
848     */
849    function isDescendant($folder) { /* {{{ */
850        /* If the current folder has no parent it cannot be a descendant */
851        if(!$this->getParent())
852            return false;
853        /* Check if the passed folder is the parent of the current folder.
854         * In that case the current folder is a subfolder of the passed folder.
855         */
856        if($this->getParent()->getID() == $folder->getID())
857            return true;
858        /* Recursively go up to the root folder */
859        return $this->getParent()->isDescendant($folder);
860    } /* }}} */
861
862    /**
863     * Check if folder has documents
864     * This function just checks if a folder has documents diregarding
865     * any access rights.
866     *
867     * @return int number of documents or false in case of an error
868     */
869    function hasDocuments() { /* {{{ */
870        $db = $this->_dms->getDB();
871        /* Do not use the cache because it may not contain all documents if
872         * the former call getDocuments() limited the number of documents
873        if (isset($this->_documents)) {
874            return count($this->_documents);
875        }
876         */
877        $queryStr = "SELECT count(*) as c FROM `tblDocuments` WHERE `folder` = " . $this->_id;
878        $resArr = $db->getResultArray($queryStr);
879        if (is_bool($resArr) && !$resArr)
880            return false;
881
882        return (int) $resArr[0]['c'];
883    } /* }}} */
884
885    /**
886     * Check if folder has document with given name
887     *
888     * @param string $name
889     * @return bool true if document exists, false if not or in case
890     * of an error
891     */
892    function hasDocumentByName($name) { /* {{{ */
893        $db = $this->_dms->getDB();
894        /* Always check the database instead of iterating over $this->_documents, because
895         * it is probably not slower
896         */
897        $queryStr = "SELECT count(*) as c FROM `tblDocuments` WHERE `folder` = " . $this->_id . " AND `name` = ".$db->qstr($name);
898        $resArr = $db->getResultArray($queryStr);
899        if (is_bool($resArr) && !$resArr)
900            return false;
901
902        return ($resArr[0]['c'] > 0);
903    } /* }}} */
904
905    /**
906     * Get all documents of the folder
907     * This function does not check for access rights. Use
908     * {@link SeedDMS_Core_DMS::filterAccess} for checking each document against
909     * the currently logged in user and the access rights.
910     *
911     * @param string $orderby if set to 'n' the list is ordered by name, otherwise
912     *        it will be ordered by sequence
913     * @param string $dir direction of sorting (asc or desc)
914     * @param integer $limit limit number of documents
915     * @param integer $offset offset in retrieved list of documents
916     * @return SeedDMS_Core_Document[]|bool list of documents or false in case of an error
917     */
918    function getDocuments($orderby="", $dir="asc", $limit=0, $offset=0) { /* {{{ */
919        $db = $this->_dms->getDB();
920
921        if (!isset($this->_documents)) {
922            $queryStr = "SELECT `tblDocuments`.*, `tblDocumentLocks`.`userID` as `lock` FROM `tblDocuments` LEFT JOIN `tblDocumentLocks` ON `tblDocuments`.`id` = `tblDocumentLocks`.`document` WHERE `folder` = " . $this->_id;
923            if ($orderby && $orderby[0]=="n") $queryStr .= " ORDER BY `name`";
924            elseif($orderby && $orderby[0]=="s") $queryStr .= " ORDER BY `sequence`";
925            elseif($orderby && $orderby[0]=="d") $queryStr .= " ORDER BY `date`";
926            if($dir == 'desc')
927                $queryStr .= " DESC";
928            if(is_int($limit) && $limit > 0) {
929                $queryStr .= " LIMIT ".$limit;
930                if(is_int($offset) && $offset > 0)
931                    $queryStr .= " OFFSET ".$offset;
932            }
933
934            $resArr = $db->getResultArray($queryStr);
935            if (is_bool($resArr) && !$resArr)
936                return false;
937
938            $this->_documents = array();
939            $classname = $this->_dms->getClassname('document');
940            foreach ($resArr as $row) {
941                    $row['lock'] = !$row['lock'] ? -1 : $row['lock'];
942//                array_push($this->_documents, $this->_dms->getDocument($row["id"]));
943                array_push($this->_documents, $classname::getInstanceByData($row, $this->_dms));
944            }
945        }
946        return $this->_documents;
947    } /* }}} */
948
949    /**
950     * Count all documents and subfolders of the folder
951     *
952     * This function also counts documents and folders of subfolders, so
953     * basically it works like recursively counting children.
954     *
955     * This function checks for access rights up the given limit. If more
956     * documents or folders are found, the returned value will be the number
957     * of objects available and the precise flag in the return array will be
958     * set to false. This number should not be revelead to the
959     * user, because it allows to gain information about the existens of
960     * objects without access right.
961     * Setting the parameter $limit to 0 will turn off access right checking
962     * which is reasonable if the $user is an administrator.
963     *
964     * @param SeedDMS_Core_User $user
965     * @param integer $limit maximum number of folders and documents that will
966     *        be precisly counted by taken the access rights into account
967     * @return array|bool with four elements 'document_count', 'folder_count'
968     *        'document_precise', 'folder_precise' holding
969     * the counted number and a flag if the number is precise.
970     * @internal param string $orderby if set to 'n' the list is ordered by name, otherwise
971     *        it will be ordered by sequence
972     */
973    function countChildren($user, $limit=10000) { /* {{{ */
974        $db = $this->_dms->getDB();
975
976        $pathPrefix="";
977        $path = $this->getPath();
978        foreach ($path as $f) {
979            $pathPrefix .= ":".$f->getID();
980        }
981        if (strlen($pathPrefix)>1) {
982            $pathPrefix .= ":";
983        }
984
985        $queryStr = "SELECT id FROM `tblFolders` WHERE `folderList` like '".$pathPrefix. "%'";
986        $resArr = $db->getResultArray($queryStr);
987        if (is_bool($resArr) && !$resArr)
988            return false;
989
990        $result = array();
991
992        $folders = array();
993        $folderids = array($this->_id);
994        $cfolders = count($resArr);
995        if($cfolders < $limit) {
996            foreach ($resArr as $row) {
997                $folder = $this->_dms->getFolder($row["id"]);
998                if ($folder->getAccessMode($user) >= M_READ) {
999                    array_push($folders, $folder);
1000                    array_push($folderids, $row['id']);
1001                }
1002            }
1003            $result['folder_count'] = count($folders);
1004            $result['folder_precise'] = true;
1005        } else {
1006            foreach ($resArr as $row) {
1007                array_push($folderids, $row['id']);
1008            }
1009            $result['folder_count'] = $cfolders;
1010            $result['folder_precise'] = false;
1011        }
1012
1013        $documents = array();
1014        if($folderids) {
1015            $queryStr = "SELECT id FROM `tblDocuments` WHERE `folder` in (".implode(',', $folderids). ")";
1016            $resArr = $db->getResultArray($queryStr);
1017            if (is_bool($resArr) && !$resArr)
1018                return false;
1019
1020            $cdocs = count($resArr);
1021            if($cdocs < $limit) {
1022                foreach ($resArr as $row) {
1023                    $document = $this->_dms->getDocument($row["id"]);
1024                    if ($document->getAccessMode($user) >= M_READ)
1025                        array_push($documents, $document);
1026                }
1027                $result['document_count'] = count($documents);
1028                $result['document_precise'] = true;
1029            } else {
1030                $result['document_count'] = $cdocs;
1031                $result['document_precise'] = false;
1032            }
1033        }
1034
1035        return $result;
1036    } /* }}} */
1037
1038    // $comment will be used for both document and version leaving empty the version_comment 
1039    /**
1040     * Add a new document to the folder
1041     * This function will add a new document and its content from a given file.
1042     * It does not check for access rights on the folder. The new documents
1043     * default access right is read only and the access right is inherited.
1044     *
1045     * @param string $name name of new document
1046     * @param string $comment comment of new document
1047     * @param integer $expires expiration date as a unix timestamp or 0 for no
1048     *        expiration date
1049     * @param object $owner owner of the new document
1050     * @param SeedDMS_Core_User $keywords keywords of new document
1051     * @param SeedDMS_Core_DocumentCategory[] $categories list of category objects
1052     * @param string $tmpFile the path of the file containing the content
1053     * @param string $orgFileName the original file name
1054     * @param string $fileType usually the extension of the filename
1055     * @param string $mimeType mime type of the content
1056     * @param float $sequence position of new document within the folder
1057     * @param array $reviewers list of users who must review this document
1058     * @param array $approvers list of users who must approve this document
1059     * @param int|string $reqversion version number of the content
1060     * @param string $version_comment comment of the content. If left empty
1061     *        the $comment will be used.
1062     * @param array $attributes list of document attributes. The element key
1063     *        must be the id of the attribute definition.
1064     * @param array $version_attributes list of document version attributes.
1065     *        The element key must be the id of the attribute definition.
1066     * @param SeedDMS_Core_Workflow $workflow
1067     * @return array|bool false in case of error, otherwise an array
1068     *        containing two elements. The first one is the new document, the
1069     * second one is the result set returned when inserting the content.
1070     */
1071    function addDocument($name, $comment, $expires, $owner, $keywords, $categories, $tmpFile, $orgFileName, $fileType, $mimeType, $sequence, $reviewers=array(), $approvers=array(),$reqversion=0,$version_comment="", $attributes=array(), $version_attributes=array(), $workflow=null) { /* {{{ */
1072        $db = $this->_dms->getDB();
1073
1074        $expires = (!$expires) ? 0 : $expires;
1075
1076        // Must also ensure that the document has a valid folderList.
1077        $pathPrefix="";
1078        $path = $this->getPath();
1079        foreach ($path as $f) {
1080            $pathPrefix .= ":".$f->getID();
1081        }
1082        if (strlen($pathPrefix)>1) {
1083            $pathPrefix .= ":";
1084        }
1085
1086        $db->startTransaction();
1087
1088        $queryStr = "INSERT INTO `tblDocuments` (`name`, `comment`, `date`, `expires`, `owner`, `folder`, `folderList`, `inheritAccess`, `defaultAccess`, `locked`, `keywords`, `sequence`) VALUES ".
1089                    "(".$db->qstr($name).", ".$db->qstr($comment).", ".$db->getCurrentTimestamp().", ".(int) $expires.", ".$owner->getID().", ".$this->_id.",".$db->qstr($pathPrefix).", 1, ".M_READ.", -1, ".$db->qstr($keywords).", " . $sequence . ")";
1090        if (!$db->getResult($queryStr)) {
1091            $db->rollbackTransaction();
1092            return false;
1093        }
1094
1095        $document = $this->_dms->getDocument($db->getInsertID('tblDocuments'));
1096
1097        $res = $document->addContent($version_comment, $owner, $tmpFile, $orgFileName, $fileType, $mimeType, $reviewers, $approvers, $reqversion, $version_attributes, $workflow);
1098
1099        if (is_bool($res) && !$res) {
1100            $db->rollbackTransaction();
1101            return false;
1102        }
1103
1104        if($categories) {
1105            if(!$document->setCategories($categories)) {
1106                $document->remove();
1107                $db->rollbackTransaction();
1108                return false;
1109            }
1110        }
1111
1112        if($attributes) {
1113            foreach($attributes as $attrdefid=>$attribute) {
1114                /* $attribute can be a string or an array */
1115                if($attribute) {
1116                    if($attrdef = $this->_dms->getAttributeDefinition($attrdefid)) {
1117                        if(!$document->setAttributeValue($attrdef, $attribute)) {
1118                            $document->remove();
1119                            $db->rollbackTransaction();
1120                            return false;
1121                        }
1122                    } else {
1123                        $document->remove();
1124                        $db->rollbackTransaction();
1125                        return false;
1126                    }
1127                }
1128            }
1129        }
1130
1131        $db->commitTransaction();
1132
1133        /* Check if 'onPostAddDocument' callback is set */
1134        if(isset($this->_dms->callbacks['onPostAddDocument'])) {
1135            foreach($this->_dms->callbacks['onPostAddDocument'] as $callback) {
1136                    /** @noinspection PhpStatementHasEmptyBodyInspection */
1137                    if(!call_user_func($callback[0], $callback[1], $document)) {
1138                }
1139            }
1140        }
1141
1142        return array($document, $res);
1143    } /* }}} */
1144
1145    /**
1146     * Remove a single folder
1147     *
1148     * Removes just a single folder, but not its subfolders or documents
1149     * This function will fail if the folder has subfolders or documents
1150     * because of referencial integrity errors.
1151     *
1152     * @return boolean true on success, false in case of an error
1153     */
1154    protected function removeFromDatabase() { /* {{{ */
1155        $db = $this->_dms->getDB();
1156
1157        /* Check if 'onPreRemoveFolder' callback is set */
1158        if(isset($this->_dms->callbacks['onPreRemoveFromDatabaseFolder'])) {
1159            foreach($this->_dms->callbacks['onPreRemoveFromDatabaseFolder'] as $callback) {
1160                $ret = call_user_func($callback[0], $callback[1], $this);
1161                if(is_bool($ret))
1162                    return $ret;
1163            }
1164        }
1165
1166        $db->startTransaction();
1167        // unset homefolder as it will no longer exist
1168        $queryStr = "UPDATE `tblUsers` SET `homefolder`=NULL WHERE `homefolder` =  " . $this->_id;
1169        if (!$db->getResult($queryStr)) {
1170            $db->rollbackTransaction();
1171            return false;
1172        }
1173
1174        // Remove database entries
1175        $queryStr = "DELETE FROM `tblFolders` WHERE `id` =  " . $this->_id;
1176        if (!$db->getResult($queryStr)) {
1177            $db->rollbackTransaction();
1178            return false;
1179        }
1180        $queryStr = "DELETE FROM `tblFolderAttributes` WHERE `folder` =  " . $this->_id;
1181        if (!$db->getResult($queryStr)) {
1182            $db->rollbackTransaction();
1183            return false;
1184        }
1185        $queryStr = "DELETE FROM `tblACLs` WHERE `target` = ". $this->_id. " AND `targetType` = " . T_FOLDER;
1186        if (!$db->getResult($queryStr)) {
1187            $db->rollbackTransaction();
1188            return false;
1189        }
1190
1191        $queryStr = "DELETE FROM `tblNotify` WHERE `target` = ". $this->_id. " AND `targetType` = " . T_FOLDER;
1192        if (!$db->getResult($queryStr)) {
1193            $db->rollbackTransaction();
1194            return false;
1195        }
1196        $db->commitTransaction();
1197
1198        /* Check if 'onPostRemoveFolder' callback is set */
1199        if(isset($this->_dms->callbacks['onPostRemoveFromDatabaseFolder'])) {
1200            foreach($this->_dms->callbacks['onPostRemoveFromDatabaseFolder'] as $callback) {
1201                /** @noinspection PhpStatementHasEmptyBodyInspection */
1202                if(!call_user_func($callback[0], $callback[1], $this->_id)) {
1203                }
1204            }
1205        }
1206
1207        return true;
1208    } /* }}} */
1209
1210    /**
1211     * Remove recursively a folder
1212     *
1213     * Removes a folder, all its subfolders and documents
1214     * This method triggers the callbacks onPreRemoveFolder and onPostRemoveFolder.
1215     * If onPreRemoveFolder returns a boolean then this method will return
1216     * imediately with the value returned by the callback. Otherwise the
1217     * regular removal is executed, which in turn
1218     * triggers further onPreRemoveFolder and onPostRemoveFolder callbacks
1219     * and its counterparts for documents (onPreRemoveDocument, onPostRemoveDocument).
1220     *
1221     * @return boolean true on success, false in case of an error
1222     */
1223    function remove() { /* {{{ */
1224        /** @noinspection PhpUnusedLocalVariableInspection */
1225        $db = $this->_dms->getDB();
1226
1227        // Do not delete the root folder.
1228        if ($this->_id == $this->_dms->rootFolderID || !isset($this->_parentID) || ($this->_parentID == null) || ($this->_parentID == "") || ($this->_parentID == 0)) {
1229            return false;
1230        }
1231
1232        /* Check if 'onPreRemoveFolder' callback is set */
1233        if(isset($this->_dms->callbacks['onPreRemoveFolder'])) {
1234            foreach($this->_dms->callbacks['onPreRemoveFolder'] as $callback) {
1235                $ret = call_user_func($callback[0], $callback[1], $this);
1236                if(is_bool($ret))
1237                    return $ret;
1238            }
1239        }
1240
1241        //Entfernen der Unterordner und Dateien
1242        $res = $this->getSubFolders();
1243        if (is_bool($res) && !$res) return false;
1244        $res = $this->getDocuments();
1245        if (is_bool($res) && !$res) return false;
1246
1247        foreach ($this->_subFolders as $subFolder) {
1248            $res = $subFolder->remove();
1249            if (!$res) {
1250                return false;
1251            }
1252        }
1253
1254        foreach ($this->_documents as $document) {
1255            $res = $document->remove();
1256            if (!$res) {
1257                return false;
1258            }
1259        }
1260
1261        $ret = $this->removeFromDatabase();
1262        if(!$ret)
1263            return $ret;
1264
1265        /* Check if 'onPostRemoveFolder' callback is set */
1266        if(isset($this->_dms->callbacks['onPostRemoveFolder'])) {
1267            foreach($this->_dms->callbacks['onPostRemoveFolder'] as $callback) {
1268                call_user_func($callback[0], $callback[1], $this);
1269            }
1270        }
1271
1272        return $ret;
1273    } /* }}} */
1274
1275    /**
1276     * Empty recursively a folder
1277     *
1278     * Removes all subfolders and documents of a folder but not the folder itself
1279     * This method will call remove() on all its children.
1280     * This method triggers the callbacks onPreEmptyFolder and onPostEmptyFolder.
1281     * If onPreEmptyFolder returns a boolean then this method will return
1282     * imediately.
1283     * Be aware that the recursive calls of remove() will trigger the callbacks
1284     * onPreRemoveFolder, onPostRemoveFolder, onPreRemoveDocument and onPostRemoveDocument.
1285     *
1286     * @return boolean true on success, false in case of an error
1287     */
1288    function emptyFolder() { /* {{{ */
1289        /** @noinspection PhpUnusedLocalVariableInspection */
1290        $db = $this->_dms->getDB();
1291
1292        /* Check if 'onPreEmptyFolder' callback is set */
1293        if(isset($this->_dms->callbacks['onPreEmptyFolder'])) {
1294            foreach($this->_dms->callbacks['onPreEmptyFolder'] as $callback) {
1295                $ret = call_user_func($callback[0], $callback[1], $this);
1296                if(is_bool($ret))
1297                    return $ret;
1298            }
1299        }
1300
1301        //Entfernen der Unterordner und Dateien
1302        $res = $this->getSubFolders();
1303        if (is_bool($res) && !$res) return false;
1304        $res = $this->getDocuments();
1305        if (is_bool($res) && !$res) return false;
1306
1307        foreach ($this->_subFolders as $subFolder) {
1308            $res = $subFolder->remove();
1309            if (!$res) {
1310                return false;
1311            }
1312        }
1313
1314        foreach ($this->_documents as $document) {
1315            $res = $document->remove();
1316            if (!$res) {
1317                return false;
1318            }
1319        }
1320
1321        /* Check if 'onPostEmptyFolder' callback is set */
1322        if(isset($this->_dms->callbacks['onPostEmptyFolder'])) {
1323            foreach($this->_dms->callbacks['onPostEmptyFolder'] as $callback) {
1324                call_user_func($callback[0], $callback[1], $this);
1325            }
1326        }
1327
1328        return true;
1329    } /* }}} */
1330
1331    /**
1332     * Returns a list of access privileges
1333     *
1334     * If the folder inherits the access privileges from the parent folder
1335     * those will be returned.
1336     * $mode and $op can be set to restrict the list of returned access
1337     * privileges. If $mode is set to M_ANY no restriction will apply
1338     * regardless of the value of $op. The returned array contains a list
1339     * of {@link SeedDMS_Core_UserAccess} and
1340     * {@link SeedDMS_Core_GroupAccess} objects. Even if the document
1341     * has no access list the returned array contains the two elements
1342     * 'users' and 'groups' which are than empty. The methode returns false
1343     * if the function fails.
1344     * 
1345     * @param integer $mode access mode (defaults to M_ANY)
1346     * @param integer $op operation (defaults to O_EQ)
1347     * @return bool|SeedDMS_Core_GroupAccess|SeedDMS_Core_UserAccess
1348     */
1349    function getAccessList($mode = M_ANY, $op = O_EQ) { /* {{{ */
1350        $db = $this->_dms->getDB();
1351
1352        if ($this->inheritsAccess()) {
1353            /* Access is supposed to be inherited but it could be that there
1354             * is no parent because the configured root folder id is somewhere
1355             * below the actual root folder.
1356             */
1357            $res = $this->getParent();
1358            if ($res)
1359                return $this->_parent->getAccessList($mode, $op);
1360        }
1361
1362        if (!isset($this->_accessList[$mode])) {
1363            if ($op!=O_GTEQ && $op!=O_LTEQ && $op!=O_EQ) {
1364                return false;
1365            }
1366            $modeStr = "";
1367            if ($mode!=M_ANY) {
1368                $modeStr = " AND `mode`".$op.(int)$mode;
1369            }
1370            $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_FOLDER.
1371                " AND `target` = " . $this->_id .    $modeStr . " ORDER BY `targetType`";
1372            $resArr = $db->getResultArray($queryStr);
1373            if (is_bool($resArr) && !$resArr)
1374                return false;
1375
1376            $this->_accessList[$mode] = array("groups" => array(), "users" => array());
1377            foreach ($resArr as $row) {
1378                if ($row["userID"] != -1)
1379                    array_push($this->_accessList[$mode]["users"], new SeedDMS_Core_UserAccess($this->_dms->getUser($row["userID"]), (int) $row["mode"]));
1380                else //if ($row["groupID"] != -1)
1381                    array_push($this->_accessList[$mode]["groups"], new SeedDMS_Core_GroupAccess($this->_dms->getGroup($row["groupID"]), (int) $row["mode"]));
1382            }
1383        }
1384
1385        return $this->_accessList[$mode];
1386    } /* }}} */
1387
1388    /**
1389     * Delete all entries for this folder from the access control list
1390     *
1391     * @param boolean $noclean set to true if notifier list shall not be clean up
1392     * @return boolean true if operation was successful otherwise false
1393     */
1394    function clearAccessList($noclean=false) { /* {{{ */
1395        $db = $this->_dms->getDB();
1396
1397        $queryStr = "DELETE FROM `tblACLs` WHERE `targetType` = " . T_FOLDER . " AND `target` = " . $this->_id;
1398        if (!$db->getResult($queryStr))
1399            return false;
1400
1401        unset($this->_accessList);
1402
1403        if(!$noclean)
1404            $this->cleanNotifyList();
1405
1406        return true;
1407    } /* }}} */
1408
1409    /**
1410     * Add access right to folder
1411     * This function may change in the future. Instead of passing the a flag
1412     * and a user/group id a user or group object will be expected.
1413     *
1414     * @param integer $mode access mode
1415     * @param integer $userOrGroupID id of user or group
1416     * @param integer $isUser set to 1 if $userOrGroupID is the id of a
1417     *        user
1418     * @return bool
1419     */
1420    function addAccess($mode, $userOrGroupID, $isUser) { /* {{{ */
1421        $db = $this->_dms->getDB();
1422
1423        if($mode < M_NONE || $mode > M_ALL)
1424            return false;
1425
1426        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1427
1428        /* Adding a second access right will return false */
1429        $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_FOLDER.
1430                " AND `target` = " . $this->_id . " AND ". $userOrGroup . " = ". (int) $userOrGroupID;
1431        $resArr = $db->getResultArray($queryStr);
1432        if (is_bool($resArr) || $resArr)
1433            return false;
1434
1435        $queryStr = "INSERT INTO `tblACLs` (`target`, `targetType`, ".$userOrGroup.", `mode`) VALUES 
1436                    (".$this->_id.", ".T_FOLDER.", " . (int) $userOrGroupID . ", " .(int) $mode. ")";
1437        if (!$db->getResult($queryStr))
1438            return false;
1439
1440        unset($this->_accessList);
1441
1442        // Update the notify list, if necessary.
1443        if ($mode == M_NONE) {
1444            $this->removeNotify($userOrGroupID, $isUser);
1445        }
1446
1447        return true;
1448    } /* }}} */
1449
1450    /**
1451     * Change access right of folder
1452     * This function may change in the future. Instead of passing the a flag
1453     * and a user/group id a user or group object will be expected.
1454     *
1455     * @param integer $newMode access mode
1456     * @param integer $userOrGroupID id of user or group
1457     * @param integer $isUser set to 1 if $userOrGroupID is the id of a
1458     *        user
1459     * @return bool
1460     */
1461    function changeAccess($newMode, $userOrGroupID, $isUser) { /* {{{ */
1462        $db = $this->_dms->getDB();
1463
1464        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1465
1466        /* Get the old access right */
1467        $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_FOLDER.
1468                " AND `target` = " . $this->_id . " AND ". $userOrGroup . " = ". (int) $userOrGroupID;
1469        $resArr = $db->getResultArray($queryStr);
1470        if (is_bool($resArr) && $resArr == false)
1471            return false;
1472
1473        $oldmode = $resArr[0]['mode'];
1474
1475        $queryStr = "UPDATE `tblACLs` SET `mode` = " . (int) $newMode . " WHERE `targetType` = ".T_FOLDER." AND `target` = " . $this->_id . " AND " . $userOrGroup . " = " . (int) $userOrGroupID;
1476        if (!$db->getResult($queryStr))
1477            return false;
1478
1479        unset($this->_accessList);
1480
1481        // Update the notify list, if necessary.
1482        if ($newMode == M_NONE) {
1483            $this->removeNotify($userOrGroupID, $isUser);
1484        }
1485
1486        return $oldmode;
1487    } /* }}} */
1488
1489    /**
1490     * @param $userOrGroupID
1491     * @param $isUser
1492     * @return bool
1493     */
1494    function removeAccess($userOrGroupID, $isUser) { /* {{{ */
1495        $db = $this->_dms->getDB();
1496
1497        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1498
1499        $queryStr = "DELETE FROM `tblACLs` WHERE `targetType` = ".T_FOLDER." AND `target` = ".$this->_id." AND ".$userOrGroup." = " . (int) $userOrGroupID;
1500        if (!$db->getResult($queryStr))
1501            return false;
1502
1503        unset($this->_accessList);
1504
1505        // Update the notify list, if necessary.
1506        $mode = ($isUser ? $this->getAccessMode($this->_dms->getUser($userOrGroupID)) : $this->getGroupAccessMode($this->_dms->getGroup($userOrGroupID)));
1507        if ($mode == M_NONE) {
1508            $this->removeNotify($userOrGroupID, $isUser);
1509        }
1510
1511        return true;
1512    } /* }}} */
1513
1514    /**
1515     * Get the access mode of a user on the folder
1516     *
1517     * The access mode is either M_READ, M_READWRITE, M_ALL, or M_NONE.
1518     * It is determined
1519     * - by the user (admins and owners have always access mode M_ALL)
1520     * - by the access list for the user (possibly inherited)
1521     * - by the default access mode
1522     *
1523     * This function returns the access mode for a given user. An administrator
1524     * and the owner of the folder has unrestricted access. A guest user has
1525     * read only access or no access if access rights are further limited
1526     * by access control lists all the default access.
1527     * All other users have access rights according
1528     * to the access control lists or the default access. This function will
1529     * recursively check for access rights of parent folders if access rights
1530     * are inherited.
1531     *
1532     * Before checking the access itself a callback 'onCheckAccessFolder'
1533     * is called. If it returns a value > 0, then this will be returned by this
1534     * method without any further checks. The optional paramater $context
1535     * will be passed as a third parameter to the callback. It contains
1536     * the operation for which the access mode is retrieved. It is for example
1537     * set to 'removeDocument' if the access mode is used to check for sufficient
1538     * permission on deleting a document. This callback could be used to
1539     * override any existing access mode in a certain context.
1540     *
1541     * @param SeedDMS_Core_User $user user for which access shall be checked
1542     * @param string $context context in which the access mode is requested
1543     * @return integer access mode
1544     */
1545    function getAccessMode($user, $context='') { /* {{{ */
1546        if(!$user)
1547            return M_NONE;
1548
1549        /* Check if 'onCheckAccessFolder' callback is set */
1550        if(isset($this->_dms->callbacks['onCheckAccessFolder'])) {
1551            foreach($this->_dms->callbacks['onCheckAccessFolder'] as $callback) {
1552                if(($ret = call_user_func($callback[0], $callback[1], $this, $user, $context)) > 0) {
1553                    return $ret;
1554                }
1555            }
1556        }
1557
1558        /* Administrators have unrestricted access */
1559        if ($user->isAdmin()) return M_ALL;
1560
1561        /* The owner of the folder has unrestricted access */
1562        if ($user->getID() == $this->_ownerID) return M_ALL;
1563
1564        /* Check ACLs */
1565        $accessList = $this->getAccessList();
1566        if (!$accessList) return false;
1567
1568        /** @var SeedDMS_Core_UserAccess $userAccess */
1569        foreach ($accessList["users"] as $userAccess) {
1570            if ($userAccess->getUserID() == $user->getID()) {
1571                $mode = $userAccess->getMode();
1572                if ($user->isGuest()) {
1573                    if ($mode >= M_READ) $mode = M_READ;
1574                }
1575                return $mode;
1576            }
1577        }
1578
1579        /* Get the highest right defined by a group */
1580        if($accessList['groups']) {
1581            $mode = 0;
1582            /** @var SeedDMS_Core_GroupAccess $groupAccess */
1583            foreach ($accessList["groups"] as $groupAccess) {
1584                if ($user->isMemberOfGroup($groupAccess->getGroup())) {
1585                    if ($groupAccess->getMode() > $mode)
1586                        $mode = $groupAccess->getMode();
1587                }
1588            }
1589            if($mode) {
1590                if ($user->isGuest()) {
1591                    if ($mode >= M_READ) $mode = M_READ;
1592                }
1593                return $mode;
1594            }
1595        }
1596
1597        $mode = $this->getDefaultAccess();
1598        if ($user->isGuest()) {
1599            if ($mode >= M_READ) $mode = M_READ;
1600        }
1601        return $mode;
1602    } /* }}} */
1603
1604    /**
1605     * Get the access mode for a group on the folder
1606     * This function returns the access mode for a given group. The algorithmn
1607     * applied to get the access mode is the same as describe at
1608     * {@link getAccessMode}
1609     *
1610     * @param SeedDMS_Core_Group $group group for which access shall be checked
1611     * @return integer access mode
1612     */
1613    function getGroupAccessMode($group) { /* {{{ */
1614        $highestPrivileged = M_NONE;
1615        $foundInACL = false;
1616        $accessList = $this->getAccessList();
1617        if (!$accessList)
1618            return false;
1619
1620        /** @var SeedDMS_Core_GroupAccess $groupAccess */
1621        foreach ($accessList["groups"] as $groupAccess) {
1622            if ($groupAccess->getGroupID() == $group->getID()) {
1623                $foundInACL = true;
1624                if ($groupAccess->getMode() > $highestPrivileged)
1625                    $highestPrivileged = $groupAccess->getMode();
1626                if ($highestPrivileged == M_ALL) /* no need to check further */
1627                    return $highestPrivileged;
1628            }
1629        }
1630        if ($foundInACL)
1631            return $highestPrivileged;
1632
1633        /* Take default access */
1634        return $this->getDefaultAccess();
1635    } /* }}} */
1636
1637    /** @noinspection PhpUnusedParameterInspection */
1638    /**
1639     * Get a list of all notification
1640     * This function returns all users and groups that have registerd a
1641     * notification for the folder
1642     *
1643     * @param integer $type type of notification (not yet used)
1644     * @param bool $incdisabled set to true if disabled user shall be included
1645     * @return SeedDMS_Core_User[]|SeedDMS_Core_Group[]|bool array with a the elements 'users' and 'groups' which
1646     *        contain a list of users and groups.
1647     */
1648    function getNotifyList($type=0, $incdisabled=false) { /* {{{ */
1649        if (empty($this->_notifyList)) {
1650            $db = $this->_dms->getDB();
1651
1652            $queryStr ="SELECT * FROM `tblNotify` WHERE `targetType` = " . T_FOLDER . " AND `target` = " . $this->_id;
1653            $resArr = $db->getResultArray($queryStr);
1654            if (is_bool($resArr) && $resArr == false)
1655                return false;
1656
1657            $this->_notifyList = array("groups" => array(), "users" => array());
1658            foreach ($resArr as $row)
1659            {
1660                if ($row["userID"] != -1) {
1661                    $u = $this->_dms->getUser($row["userID"]);
1662                    if($u && (!$u->isDisabled() || $incdisabled))
1663                        array_push($this->_notifyList["users"], $u);
1664                } else {//if ($row["groupID"] != -1)
1665                    $g = $this->_dms->getGroup($row["groupID"]);
1666                    if($g)
1667                        array_push($this->_notifyList["groups"], $g);
1668                }
1669            }
1670        }
1671        return $this->_notifyList;
1672    } /* }}} */
1673
1674    /**
1675     * Make sure only users/groups with read access are in the notify list
1676     *
1677     */
1678    function cleanNotifyList() { /* {{{ */
1679        // If any of the notification subscribers no longer have read access,
1680        // remove their subscription.
1681        if (empty($this->_notifyList))
1682            $this->getNotifyList();
1683
1684        /* Make a copy of both notifier lists because removeNotify will empty
1685         * $this->_notifyList and the second foreach will not work anymore.
1686         */
1687        /** @var SeedDMS_Core_User[] $nusers */
1688        $nusers = $this->_notifyList["users"];
1689        $ngroups = $this->_notifyList["groups"];
1690        foreach ($nusers as $u) {
1691            if ($this->getAccessMode($u) < M_READ) {
1692                $this->removeNotify($u->getID(), true);
1693            }
1694        }
1695
1696        /** @var SeedDMS_Core_Group[] $ngroups */
1697        foreach ($ngroups as $g) {
1698            if ($this->getGroupAccessMode($g) < M_READ) {
1699                $this->removeNotify($g->getID(), false);
1700            }
1701        }
1702    } /* }}} */
1703
1704    /**
1705     * Add a user/group to the notification list
1706     * This function does not check if the currently logged in user
1707     * is allowed to add a notification. This must be checked by the calling
1708     * application.
1709     *
1710     * @param integer $userOrGroupID
1711     * @param boolean $isUser true if $userOrGroupID is a user id otherwise false
1712     * @return integer error code
1713     *    -1: Invalid User/Group ID.
1714     *    -2: Target User / Group does not have read access.
1715     *    -3: User is already subscribed.
1716     *    -4: Database / internal error.
1717     *     0: Update successful.
1718     */
1719    function addNotify($userOrGroupID, $isUser) { /* {{{ */
1720        $db = $this->_dms->getDB();
1721
1722        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1723
1724        /* Verify that user / group exists */
1725        /** @var SeedDMS_Core_User|SeedDMS_Core_Group $obj */
1726        $obj = ($isUser ? $this->_dms->getUser($userOrGroupID) : $this->_dms->getGroup($userOrGroupID));
1727        if (!is_object($obj)) {
1728            return -1;
1729        }
1730
1731        /* Verify that the requesting user has permission to add the target to
1732         * the notification system.
1733         */
1734        /*
1735         * The calling application should enforce the policy on who is allowed
1736         * to add someone to the notification system. If is shall remain here
1737         * the currently logged in user should be passed to this function
1738         *
1739        GLOBAL $user;
1740        if ($user->isGuest()) {
1741            return -2;
1742        }
1743        if (!$user->isAdmin()) {
1744            if ($isUser) {
1745                if ($user->getID() != $obj->getID()) {
1746                    return -2;
1747                }
1748            }
1749            else {
1750                if (!$obj->isMember($user)) {
1751                    return -2;
1752                }
1753            }
1754        }
1755        */
1756
1757        //
1758        // Verify that user / group has read access to the document.
1759        //
1760        if ($isUser) {
1761            // Users are straightforward to check.
1762            if ($this->getAccessMode($obj) < M_READ) {
1763                return -2;
1764            }
1765        }
1766        else {
1767            // FIXME: Why not check the access list first and if this returns
1768            // not result, then use the default access?
1769            // Groups are a little more complex.
1770            if ($this->getDefaultAccess() >= M_READ) {
1771                // If the default access is at least READ-ONLY, then just make sure
1772                // that the current group has not been explicitly excluded.
1773                $acl = $this->getAccessList(M_NONE, O_EQ);
1774                $found = false;
1775                /** @var SeedDMS_Core_GroupAccess $group */
1776                foreach ($acl["groups"] as $group) {
1777                    if ($group->getGroupID() == $userOrGroupID) {
1778                        $found = true;
1779                        break;
1780                    }
1781                }
1782                if ($found) {
1783                    return -2;
1784                }
1785            }
1786            else {
1787                // The default access is restricted. Make sure that the group has
1788                // been explicitly allocated access to the document.
1789                $acl = $this->getAccessList(M_READ, O_GTEQ);
1790                if (is_bool($acl)) {
1791                    return -4;
1792                }
1793                $found = false;
1794                /** @var SeedDMS_Core_GroupAccess $group */
1795                foreach ($acl["groups"] as $group) {
1796                    if ($group->getGroupID() == $userOrGroupID) {
1797                        $found = true;
1798                        break;
1799                    }
1800                }
1801                if (!$found) {
1802                    return -2;
1803                }
1804            }
1805        }
1806        //
1807        // Check to see if user/group is already on the list.
1808        //
1809        $queryStr = "SELECT * FROM `tblNotify` WHERE `tblNotify`.`target` = '".$this->_id."' ".
1810            "AND `tblNotify`.`targetType` = '".T_FOLDER."' ".
1811            "AND `tblNotify`.".$userOrGroup." = '". (int) $userOrGroupID."'";
1812        $resArr = $db->getResultArray($queryStr);
1813        if (is_bool($resArr)) {
1814            return -4;
1815        }
1816        if (count($resArr)>0) {
1817            return -3;
1818        }
1819
1820        $queryStr = "INSERT INTO `tblNotify` (`target`, `targetType`, " . $userOrGroup . ") VALUES (" . $this->_id . ", " . T_FOLDER . ", " .  (int) $userOrGroupID . ")";
1821        if (!$db->getResult($queryStr))
1822            return -4;
1823
1824        unset($this->_notifyList);
1825        return 0;
1826    } /* }}} */
1827
1828    /**
1829     * Removes notify for a user or group to folder
1830     * This function does not check if the currently logged in user
1831     * is allowed to remove a notification. This must be checked by the calling
1832     * application.
1833     *
1834     * @param integer $userOrGroupID
1835     * @param boolean $isUser true if $userOrGroupID is a user id otherwise false
1836     * @param int $type type of notification (0 will delete all) Not used yet!
1837     * @return int error code
1838     *    -1: Invalid User/Group ID.
1839     * -3: User is not subscribed.
1840     * -4: Database / internal error.
1841     * 0: Update successful.
1842     */
1843    function removeNotify($userOrGroupID, $isUser, $type=0) { /* {{{ */
1844        $db = $this->_dms->getDB();
1845
1846        /* Verify that user / group exists. */
1847        $obj = ($isUser ? $this->_dms->getUser($userOrGroupID) : $this->_dms->getGroup($userOrGroupID));
1848        if (!is_object($obj)) {
1849            return -1;
1850        }
1851
1852        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1853
1854        /* Verify that the requesting user has permission to add the target to
1855         * the notification system.
1856         */
1857        /*
1858         * The calling application should enforce the policy on who is allowed
1859         * to add someone to the notification system. If is shall remain here
1860         * the currently logged in user should be passed to this function
1861         *
1862        GLOBAL  $user;
1863        if ($user->isGuest()) {
1864            return -2;
1865        }
1866        if (!$user->isAdmin()) {
1867            if ($isUser) {
1868                if ($user->getID() != $obj->getID()) {
1869                    return -2;
1870                }
1871            }
1872            else {
1873                if (!$obj->isMember($user)) {
1874                    return -2;
1875                }
1876            }
1877        }
1878        */
1879
1880        //
1881        // Check to see if the target is in the database.
1882        //
1883        $queryStr = "SELECT * FROM `tblNotify` WHERE `tblNotify`.`target` = '".$this->_id."' ".
1884            "AND `tblNotify`.`targetType` = '".T_FOLDER."' ".
1885            "AND `tblNotify`.".$userOrGroup." = '". (int) $userOrGroupID."'";
1886        $resArr = $db->getResultArray($queryStr);
1887        if (is_bool($resArr)) {
1888            return -4;
1889        }
1890        if (count($resArr)==0) {
1891            return -3;
1892        }
1893
1894        $queryStr = "DELETE FROM `tblNotify` WHERE `target` = " . $this->_id . " AND `targetType` = " . T_FOLDER . " AND " . $userOrGroup . " = " .  (int) $userOrGroupID;
1895        /* If type is given then delete only those notifications */
1896        if($type)
1897            $queryStr .= " AND `type` = ".(int) $type;
1898        if (!$db->getResult($queryStr))
1899            return -4;
1900
1901        unset($this->_notifyList);
1902        return 0;
1903    } /* }}} */
1904
1905    /**
1906     * Get List of users and groups which have read access on the document
1907     *
1908     * This function is deprecated. Use
1909     * {@see SeedDMS_Core_Folder::getReadAccessList()} instead.
1910     */
1911    function getApproversList() { /* {{{ */
1912        return $this->getReadAccessList(0, 0);
1913    } /* }}} */
1914
1915    /**
1916     * Returns a list of groups and users with read access on the folder
1917     * The list will not include any guest users,
1918     * administrators and the owner of the folder unless $listadmin resp.
1919     * $listowner is set to true.
1920     *
1921     * @param boolean $listadmin if set to true any admin will be listed too
1922     * @param boolean $listowner if set to true the owner will be listed too
1923     * @param boolean $listguest if set to true any guest will be listed too
1924     * @return array list of users and groups
1925     */
1926    function getReadAccessList($listadmin=0, $listowner=0, $listguest=0) { /* {{{ */
1927        $db = $this->_dms->getDB();
1928
1929        if (!isset($this->_readAccessList)) {
1930            $this->_readAccessList = array("groups" => array(), "users" => array());
1931            $userIDs = "";
1932            $groupIDs = "";
1933            $defAccess  = $this->getDefaultAccess();
1934
1935            /* Check if the default access is < read access or >= read access.
1936             * If default access is less than read access, then create a list
1937             * of users and groups with read access.
1938             * If default access is equal or greater then read access, then
1939             * create a list of users and groups without read access.
1940             */
1941            if ($defAccess<M_READ) {
1942                // Get the list of all users and groups that are listed in the ACL as
1943                // having read access to the folder.
1944                $tmpList = $this->getAccessList(M_READ, O_GTEQ);
1945            }
1946            else {
1947                // Get the list of all users and groups that DO NOT have read access
1948                // to the folder.
1949                $tmpList = $this->getAccessList(M_NONE, O_LTEQ);
1950            }
1951            /** @var SeedDMS_Core_GroupAccess $groupAccess */
1952            foreach ($tmpList["groups"] as $groupAccess) {
1953                $groupIDs .= (strlen($groupIDs)==0 ? "" : ", ") . $groupAccess->getGroupID();
1954            }
1955
1956            /** @var SeedDMS_Core_UserAccess $userAccess */
1957            foreach ($tmpList["users"] as $userAccess) {
1958                $user = $userAccess->getUser();
1959                if (!$listadmin && $user->isAdmin()) continue;
1960                if (!$listowner && $user->getID() == $this->_ownerID) continue;
1961                if (!$listguest && $user->isGuest()) continue;
1962                $userIDs .= (strlen($userIDs)==0 ? "" : ", ") . $userAccess->getUserID();
1963            }
1964
1965            // Construct a query against the users table to identify those users
1966            // that have read access to this folder, either directly through an
1967            // ACL entry, by virtue of ownership or by having administrative rights
1968            // on the database.
1969            $queryStr="";
1970            /* If default access is less then read, $userIDs and $groupIDs contains
1971             * a list of user with read access
1972             */
1973            if ($defAccess < M_READ) {
1974                if (strlen($groupIDs)>0) {
1975                    $queryStr = "SELECT `tblUsers`.* FROM `tblUsers` ".
1976                        "LEFT JOIN `tblGroupMembers` ON `tblGroupMembers`.`userID`=`tblUsers`.`id` ".
1977                        "WHERE `tblGroupMembers`.`groupID` IN (". $groupIDs .") ".
1978                        "AND `tblUsers`.`role` != ".SeedDMS_Core_User::role_guest." UNION ";
1979                }
1980                $queryStr .=
1981                    "SELECT `tblUsers`.* FROM `tblUsers` ".
1982                    "WHERE (`tblUsers`.`role` != ".SeedDMS_Core_User::role_guest.") ".
1983                    "AND ((`tblUsers`.`id` = ". $this->_ownerID . ") ".
1984                    "OR (`tblUsers`.`role` = ".SeedDMS_Core_User::role_admin.")".
1985                    (strlen($userIDs) == 0 ? "" : " OR (`tblUsers`.`id` IN (". $userIDs ."))").
1986                    ") ORDER BY `login`";
1987            }
1988            /* If default access is equal or greater than M_READ, $userIDs and
1989             * $groupIDs contains a list of user without read access
1990             */
1991            else {
1992                if (strlen($groupIDs)>0) {
1993                    $queryStr = "SELECT `tblUsers`.* FROM `tblUsers` ".
1994                        "LEFT JOIN `tblGroupMembers` ON `tblGroupMembers`.`userID`=`tblUsers`.`id` ".
1995                        "WHERE `tblGroupMembers`.`groupID` NOT IN (". $groupIDs .")".
1996                        "AND `tblUsers`.`role` != ".SeedDMS_Core_User::role_guest." ".
1997                        (strlen($userIDs) == 0 ? "" : " AND (`tblUsers`.`id` NOT IN (". $userIDs ."))")." UNION ";
1998                }
1999                $queryStr .=
2000                    "SELECT `tblUsers`.* FROM `tblUsers` ".
2001                    "WHERE (`tblUsers`.`id` = ". $this->_ownerID . ") ".
2002                    "OR (`tblUsers`.`role` = ".SeedDMS_Core_User::role_admin.") ".
2003                    "UNION ".
2004                    "SELECT `tblUsers`.* FROM `tblUsers` ".
2005                    "WHERE `tblUsers`.`role` != ".SeedDMS_Core_User::role_guest." ".
2006                    (strlen($userIDs) == 0 ? "" : " AND (`tblUsers`.`id` NOT IN (". $userIDs ."))").
2007                    " ORDER BY `login`";
2008            }
2009            $resArr = $db->getResultArray($queryStr);
2010            if (!is_bool($resArr)) {
2011                foreach ($resArr as $row) {
2012                    $user = $this->_dms->getUser($row['id']);
2013                    if (!$listadmin && $user->isAdmin()) continue;
2014                    if (!$listowner && $user->getID() == $this->_ownerID) continue;
2015                    $this->_readAccessList["users"][] = $user;
2016                }
2017            }
2018
2019            // Assemble the list of groups that have read access to the folder.
2020            $queryStr="";
2021            if ($defAccess < M_READ) {
2022                if (strlen($groupIDs)>0) {
2023                    $queryStr = "SELECT `tblGroups`.* FROM `tblGroups` ".
2024                        "WHERE `tblGroups`.`id` IN (". $groupIDs .") ORDER BY `name`";
2025                }
2026            }
2027            else {
2028                if (strlen($groupIDs)>0) {
2029                    $queryStr = "SELECT `tblGroups`.* FROM `tblGroups` ".
2030                        "WHERE `tblGroups`.`id` NOT IN (". $groupIDs .") ORDER BY `name`";
2031                }
2032                else {
2033                    $queryStr = "SELECT `tblGroups`.* FROM `tblGroups` ORDER BY `name`";
2034                }
2035            }
2036            if (strlen($queryStr)>0) {
2037                $resArr = $db->getResultArray($queryStr);
2038                if (!is_bool($resArr)) {
2039                    foreach ($resArr as $row) {
2040                        $group = $this->_dms->getGroup($row["id"]);
2041                        $this->_readAccessList["groups"][] = $group;
2042                    }
2043                }
2044            }
2045        }
2046        return $this->_readAccessList;
2047    } /* }}} */
2048
2049    /**
2050     * Get the internally used folderList which stores the ids of folders from
2051     * the root folder to the parent folder.
2052     *
2053     * @return string column separated list of folder ids
2054     */
2055    function getFolderList() { /* {{{ */
2056        $db = $this->_dms->getDB();
2057
2058        $queryStr = "SELECT `folderList` FROM `tblFolders` where `id` = ".$this->_id;
2059        $resArr = $db->getResultArray($queryStr);
2060        if (is_bool($resArr) && !$resArr)
2061            return false;
2062        return $resArr[0]['folderList'];
2063    } /* }}} */
2064
2065    /**
2066     * Checks the internal data of the folder and repairs it.
2067     * Currently, this function only repairs an incorrect folderList
2068     *
2069     * @return boolean true on success, otherwise false
2070     */
2071    function repair() { /* {{{ */
2072        $db = $this->_dms->getDB();
2073
2074        $curfolderlist = $this->getFolderList();
2075
2076        // calculate the folderList of the folder
2077        $parent = $this->getParent();
2078        $pathPrefix="";
2079        $path = $parent->getPath();
2080        foreach ($path as $f) {
2081            $pathPrefix .= ":".$f->getID();
2082        }
2083        if (strlen($pathPrefix)>1) {
2084            $pathPrefix .= ":";
2085        }
2086        if($curfolderlist != $pathPrefix) {
2087            $queryStr = "UPDATE `tblFolders` SET `folderList`='".$pathPrefix."' WHERE `id` = ". $this->_id;
2088            $res = $db->getResult($queryStr);
2089            if (!$res)
2090                return false;
2091        }
2092        return true;
2093    } /* }}} */
2094
2095    /**
2096     * Get the min and max sequence value for documents
2097     *
2098     * @return bool|array array with keys 'min' and 'max', false in case of an error
2099     */
2100    function getDocumentsMinMax() { /* {{{ */
2101        $db = $this->_dms->getDB();
2102
2103        $queryStr = "SELECT min(`sequence`) AS `min`, max(`sequence`) AS `max` FROM `tblDocuments` WHERE `folder` = " . (int) $this->_id;
2104        $resArr = $db->getResultArray($queryStr);
2105        if (is_bool($resArr) && $resArr == false)
2106            return false;
2107
2108        return $resArr[0];
2109    } /* }}} */
2110
2111    /**
2112     * Get the min and max sequence value for folders
2113     *
2114     * @return bool|array array with keys 'min' and 'max', false in case of an error
2115     */
2116    function getFoldersMinMax() { /* {{{ */
2117        $db = $this->_dms->getDB();
2118
2119        $queryStr = "SELECT min(`sequence`) AS `min`, max(`sequence`) AS `max` FROM `tblFolders` WHERE `parent` = " . (int) $this->_id;
2120        $resArr = $db->getResultArray($queryStr);
2121        if (is_bool($resArr) && $resArr == false)
2122            return false;
2123
2124        return $resArr[0];
2125    } /* }}} */
2126
2127    /**
2128     * Reorder documents of folder
2129     *
2130     * Fix the sequence numbers of all documents in the folder, by assigning new
2131     * numbers starting from 1 incrementing by 1. This can be necessary if sequence
2132     * numbers are not unique which makes manual reordering for documents with
2133     * identical sequence numbers impossible.
2134     *
2135     * @return bool false in case of an error, otherwise true
2136     */
2137    function reorderDocuments() { /* {{{ */
2138        $db = $this->_dms->getDB();
2139
2140        $queryStr = "SELECT `id` FROM `tblDocuments` WHERE `folder` = " . (int) $this->_id . " ORDER BY `sequence`";
2141        $resArr = $db->getResultArray($queryStr);
2142        if (is_bool($resArr) && $resArr == false)
2143            return false;
2144
2145        $db->startTransaction();
2146        $no = 1.0;
2147        foreach($resArr as $doc) {
2148            $queryStr = "UPDATE `tblDocuments` SET `sequence` = " . $no . " WHERE `id` = ". $doc['id'];
2149            if (!$db->getResult($queryStr)) {
2150                $db->rollbackTransaction();
2151                return false;
2152            }
2153            $no += 1.0;
2154        }
2155        $db->commitTransaction();
2156
2157        return true;
2158    } /* }}} */
2159
2160
2161}
2162
2163?>