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