Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
47.64% covered (danger)
47.64%
1041 / 2185
54.90% covered (warning)
54.90%
56 / 102
CRAP
0.00% covered (danger)
0.00%
0 / 1
SeedDMS_Core_DMS
47.38% covered (danger)
47.38%
1030 / 2174
54.90% covered (warning)
54.90%
56 / 102
141496.92
0.00% covered (danger)
0.00%
0 / 1
 checkIfEqual
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 inList
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
 checkDate
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 filterAccess
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
 filterUsersByAccess
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 filterDocumentLinks
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
90
 mergeAccessLists
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
306
 filterDocumentFiles
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
56
 __construct
96.55% covered (success)
96.55%
28 / 29
0.00% covered (danger)
0.00%
0 / 1
3
 getClassname
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setClassname
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getDecorators
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 addDecorator
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getDB
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDBVersion
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 checkVersion
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
7
 setRootFolderID
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 setMaxDirID
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRootFolder
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 setForceRename
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setForceLink
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setUser
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 getLoggedInUser
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDocument
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getDocumentsByUser
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDocumentsLockedByUser
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDocumentsExpired
97.73% covered (success)
97.73%
43 / 44
0.00% covered (danger)
0.00%
0 / 1
15
 getDocumentByName
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
7
 getDocumentByOriginalFilename
95.45% covered (success)
95.45%
21 / 22
0.00% covered (danger)
0.00%
0 / 1
7
 getDocumentContent
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 countTasks
0.00% covered (danger)
0.00%
0 / 119
0.00% covered (danger)
0.00%
0 / 1
1122
 getDocumentList
13.30% covered (danger)
13.30%
79 / 594
0.00% covered (danger)
0.00%
0 / 1
37779.18
 makeTimeStamp
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
23
 search
47.47% covered (danger)
47.47%
197 / 415
0.00% covered (danger)
0.00%
0 / 1
6540.66
 getFolder
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getFolderByName
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 checkFolders
96.43% covered (success)
96.43%
27 / 28
0.00% covered (danger)
0.00%
0 / 1
13
 checkDocuments
97.06% covered (success)
97.06%
33 / 34
0.00% covered (danger)
0.00%
0 / 1
16
 getUser
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
4.37
 getUserByLogin
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getUserByEmail
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getAllUsers
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addUser
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
12
 getGroup
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
4.37
 getGroupByName
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getAllGroups
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addGroup
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 getRole
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getRoleByName
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getAllRoles
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 addRole
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 getTransmittal
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getTransmittalByName
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getAllTransmittals
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 addTransmittal
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 getKeywordCategory
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 getKeywordCategoryByName
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
6
 getAllKeywordCategories
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
 getAllUserKeywordCategories
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 addKeywordCategory
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
9
 getDocumentCategory
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 getDocumentCategories
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 getDocumentCategoryByName
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 addDocumentCategory
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
7
 getNotificationsByGroup
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNotificationsByUser
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createPasswordRequest
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
5.51
 checkPasswordRequest
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 deletePasswordRequest
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getAttributeDefinition
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 getAttributeDefinitionByName
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 getAllAttributeDefinitions
82.61% covered (warning)
82.61%
19 / 23
0.00% covered (danger)
0.00%
0 / 1
12.76
 addAttributeDefinition
83.33% covered (warning)
83.33%
15 / 18
0.00% covered (danger)
0.00%
0 / 1
8.30
 getAllWorkflows
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
7.01
 getWorkflow
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 getWorkflowByName
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 addWorkflow
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
4.01
 getWorkflowState
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 getWorkflowStateByName
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 getAllWorkflowStates
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 addWorkflowState
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
4.01
 getWorkflowAction
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 getWorkflowActionByName
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 getAllWorkflowActions
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 addWorkflowAction
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
4.01
 getWorkflowTransition
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
 getDocumentsInReception
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 getDocumentsInRevision
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 getUnlinkedDocumentContent
54.55% covered (warning)
54.55%
6 / 11
0.00% covered (danger)
0.00%
0 / 1
3.85
 getNoFileSizeDocumentContent
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 getNoChecksumDocumentContent
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 getWrongFiletypeDocumentContent
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
156
 getDuplicateDocumentContent
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
5
 getDuplicateSequenceNo
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 getProcessWithoutUserGroup
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
56
 removeProcessWithoutUserGroup
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
90
 getStatisticalData
75.34% covered (warning)
75.34%
55 / 73
0.00% covered (danger)
0.00%
0 / 1
55.43
 getTimeline
76.47% covered (warning)
76.47%
13 / 17
0.00% covered (danger)
0.00%
0 / 1
6.47
 getLatestChanges
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 1
380
 setCallback
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
4.25
 addCallback
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 hasCallback
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2declare(strict_types=1);
3
4/**
5 * Implementation of the document management system
6 *
7 * @category   DMS
8 * @package    SeedDMS_Core
9 * @license    GPL 2
10 * @author     Uwe Steinmann <uwe@steinmann.cx>
11 * @copyright  2010 Uwe Steinmann
12 * @version    Release: @package_version@
13 */
14
15/**
16 * Include some files
17 */
18require_once("inc.AccessUtils.php");
19require_once("inc.FileUtils.php");
20require_once("inc.ClassAccess.php");
21require_once("inc.ClassObject.php");
22require_once("inc.ClassFolder.php");
23require_once("inc.ClassDocument.php");
24require_once("inc.ClassGroup.php");
25require_once("inc.ClassUser.php");
26require_once("inc.ClassKeywords.php");
27require_once("inc.ClassNotification.php");
28require_once("inc.ClassAttribute.php");
29
30/**
31 * Class to represent the complete document management system.
32 * This class is needed to do most of the dms operations. It needs
33 * an instance of {@link SeedDMS_Core_DatabaseAccess} to access the
34 * underlying database. Many methods are factory functions which create
35 * objects representing the entities in the dms, like folders, documents,
36 * users, or groups.
37 *
38 * Each dms has its own database for meta data and a data store for document
39 * content. Both must be specified when creating a new instance of this class.
40 * All folders and documents are organized in a hierachy like
41 * a regular file system starting with a {@link $rootFolderID}
42 *
43 * This class does not enforce any access rights on documents and folders
44 * by design. It is up to the calling application to use the methods
45 * {@link SeedDMS_Core_Folder::getAccessMode()} and
46 * {@link SeedDMS_Core_Document::getAccessMode()} and interpret them as desired.
47 * Though, there are two convenient functions to filter a list of
48 * documents/folders for which users have access rights for. See
49 * {@link filterAccess()}
50 * and {@link filterUsersByAccess()}
51 *
52 * Though, this class has a method to set the currently logged in user
53 * ({@link setUser}), it does not have to be called, because
54 * there is currently no class within the SeedDMS core which needs the logged
55 * in user. {@link SeedDMS_Core_DMS} itself does not do any user authentication.
56 * It is up to the application using this class.
57 *
58 * <code>
59 * <?php
60 * include("inc/inc.ClassDMS.php");
61 * $db = new SeedDMS_Core_DatabaseAccess($type, $hostname, $user, $passwd, $name);
62 * $db->connect() or die ("Could not connect to db-server");
63 * $dms = new SeedDMS_Core_DMS($db, $contentDir);
64 * $dms->setRootFolderID(1);
65 * ...
66 * ?>
67 * </code>
68 *
69 * @category   DMS
70 * @package    SeedDMS_Core
71 * @version    @version@
72 * @author     Uwe Steinmann <uwe@steinmann.cx>
73 * @copyright  Copyright (C) 2010, Uwe Steinmann
74 * @version    Release: @package_version@
75 */
76class SeedDMS_Core_DMS {
77    /**
78     * @var SeedDMS_Core_DatabaseAccess $db reference to database object. This must be an instance
79     *      of {@link SeedDMS_Core_DatabaseAccess}.
80     * @access protected
81     */
82    protected $db;
83
84    /**
85     * @var array $classnames list of classnames for objects being instanciate
86     *      by the dms
87     * @access protected
88     */
89    protected $classnames;
90
91    /**
92     * @var array $decorators list of decorators for objects being instanciate
93     *      by the dms
94     * @access protected
95     */
96    protected $decorators;
97
98    /**
99     * @var SeedDMS_Core_User $user reference to currently logged in user. This must be
100     *      an instance of {@link SeedDMS_Core_User}. This variable is currently not
101     *      used. It is set by {@link setUser}.
102     * @access private
103     */
104    private $user;
105
106    /**
107     * @var string $contentDir location in the file system where all the
108     *      document data is located. This should be an absolute path.
109     * @access public
110     */
111    public $contentDir;
112
113    /**
114     * @var integer $rootFolderID ID of root folder
115     * @access public
116     */
117    public $rootFolderID;
118
119    /**
120     * @var integer $maxDirID maximum number of documents per folder on the
121     *      filesystem. If this variable is set to a value != 0, the content
122     *      directory will have a two level hierarchy for document storage.
123     * @access public
124     */
125    public $maxDirID;
126
127    /**
128     * @var boolean $forceRename use renameFile() instead of copyFile() when
129     *      copying the document content into the data store. The default is
130     *      to copy the file. This parameter only affects the methods
131     *      SeedDMS_Core_Document::addDocument() and
132     *      SeedDMS_Core_Document::addDocumentFile(). Setting this to true
133     *      may save resources especially for large files.
134     * @access public
135     */
136    public $forceRename;
137
138    /**
139     * @var boolean $forceLink use linkFile() instead of copyFile() when
140     *      copying the document content into the data store. The default is
141     *      to copy the file. This parameter only affects the method
142     *      SeedDMS_Core_Document::addDocument(). Use this with care,
143     *      because it will leave the original document at its place.
144     * @access public
145     */
146    public $forceLink;
147
148    /**
149     * @var array $noReadForStatus list of status without read right
150     *      online. DO NOT USE ANYMORE. SeedDMS_Core_DocumentContent::getAccessMode()
151     *      was the only method using it, but it now takes the noReadForStatus info
152     *      from the user's role
153     * @access public
154     */
155    public $noReadForStatus;
156
157    /**
158     * @var boolean $checkWithinRootDir check if folder/document being accessed
159     *      is within the rootdir
160     * @access public
161     */
162    public $checkWithinRootDir;
163
164    /**
165     * @var string $version version of pear package
166     * @access public
167     */
168    public $version;
169
170    /**
171     * @var boolean $usecache true if internal cache shall be used
172     * @access public
173     */
174    public $usecache;
175
176    /**
177     * @var array $cache cache for various objects
178     * @access public
179     */
180    protected $cache;
181
182    /**
183     * @var array $callbacks list of methods called when certain operations,
184     * like removing a document, are executed. Set a callback with
185     * {@link SeedDMS_Core_DMS::setCallback()}.
186     * The key of the array is the internal callback function name. Each
187     * array element is an array with two elements: the function name
188     * and the parameter passed to the function.
189     *
190     * Currently implemented callbacks are:
191     *
192     * onPreRemoveDocument($user_param, $document);
193     *   called before deleting a document. If this function returns false
194     *   the document will not be deleted.
195     *
196     * onPostRemoveDocument($user_param, $document_id);
197     *   called after the successful deletion of a document.
198     *
199     * @access public
200     */
201    public $callbacks;
202
203    /**
204     * @var string last error message. This can be set by hooks to pass an
205     * error message from the hook to the application which has called the
206     * method containing the hook. For example SeedDMS_Core_Document::remove()
207     * calls the hook 'onPreRemoveDocument'. The hook function can set $dms->lasterror
208     * which can than be read when SeedDMS_Core_Document::remove() fails.
209     * This variable could be set in any SeedDMS_Core class, but is currently
210     * only set by hooks.
211     * @access public
212     */
213    public $lasterror;
214
215    /**
216     * @var SeedDMS_Core_DMS
217     */
218//    public $_dms;
219
220
221    /**
222     * Checks if two objects are equal by comparing their IDs
223     *
224     * The regular php check done by '==' compares all attributes of
225     * two objects, which is often not required. This method will first check
226     * if the objects are instances of the same class and than if they
227     * have the same id.
228     *
229     * @param object $object1 first object to be compared
230     * @param object $object2 second object to be compared
231     * @return boolean true if objects are equal, otherwise false
232     */
233    static function checkIfEqual($object1, $object2) { /* {{{ */
234        if(get_class($object1) != get_class($object2))
235            return false;
236        if($object1->getID() != $object2->getID())
237            return false;
238        return true;
239    } /* }}} */
240
241    /**
242     * Checks if a list of objects contains a single object by comparing their IDs
243     *
244     * This function is only applicable on list containing objects which have
245     * a method getID() because it is used to check if two objects are equal.
246     * The regular php check on objects done by '==' compares all attributes of
247     * two objects, which isn't required. The method will first check
248     * if the objects are instances of the same class.
249     *
250     * The result of the function can be 0 which happens if the first element
251     * of an indexed array matches.
252     *
253     * @param object $object object to look for (needle)
254     * @param array $list list of objects (haystack)
255     * @return boolean/integer index in array if object was found, otherwise false
256     */
257    static function inList($object, $list) { /* {{{ */
258        foreach($list as $i=>$item) {
259            if(get_class($item) == get_class($object) && $item->getID() == $object->getID())
260                return $i;
261        }
262        return false;
263    } /* }}} */
264
265    /**
266     * Checks if date conforms to a given format
267     *
268     * @param string $date date to be checked
269     * @param string $format format of date. Will default to 'Y-m-d H:i:s' if
270     * format is not given.
271     * @return boolean true if date is in propper format, otherwise false
272     */
273    static function checkDate($date, $format='Y-m-d H:i:s') { /* {{{ */
274        $d = DateTime::createFromFormat($format, $date);
275        return $d && $d->format($format) == $date;
276    } /* }}} */
277
278    /**
279     * Filter out objects which are not accessible in a given mode by a user.
280     *
281     * The list of objects to be checked can be of any class, but has to have
282     * a method getAccessMode($user) which checks if the given user has at
283     * least the access right on the object as passed in $minMode.
284     * Hence, passing a group instead of user is possible.
285     *
286     * This function can be used for documents and folders and calls
287     * {@link SeedDMS_Core_Folder::getAccessMode()} or
288     * {@link SeedDMS_Core_Document::getAccessMode()}. A document is also
289     * filtered out if it has no latest content, which can happen if access
290     * on documents in a certain state has been restricted.
291     *
292     * @param array $objArr list of objects (either documents or folders)
293     * @param object $user user for which access is checked
294     * @param integer $minMode minimum access mode required (M_ANY, M_NONE,
295     *        M_READ, M_READWRITE, M_ALL)
296     * @return array filtered list of objects
297     */
298    static function filterAccess($objArr, $user, $minMode) { /* {{{ */
299        if (!is_array($objArr)) {
300            return array();
301        }
302        $newArr = array();
303        foreach ($objArr as $obj) {
304            if ($obj->getAccessMode($user) >= $minMode) {
305                $dms = $obj->getDMS();
306                if($obj->isType('document')) {
307                    if($obj->getLatestContent())
308                        array_push($newArr, $obj);
309                } else {
310                    array_push($newArr, $obj);
311                }
312            }
313        }
314        return $newArr;
315    } /* }}} */
316
317    /**
318     * Filter out users which cannot access an object in a given mode.
319     *
320     * The list of users to be checked can be of any class, but has to have
321     * a method getAccessMode($user) which checks if a user has at least the
322     * access right as passed in $minMode. Hence, passing a list of groups
323     * instead of users is possible.
324     *
325     * @param object $obj object that shall be accessed
326     * @param array $users list of users/groups which are to check for sufficient
327     *        access rights
328     * @param integer $minMode minimum access right on the object for each user
329     *        (M_ANY, M_NONE, M_READ, M_READWRITE, M_ALL)
330     * @return array filtered list of users
331     */
332    static function filterUsersByAccess($obj, $users, $minMode) { /* {{{ */
333        $newArr = array();
334        foreach ($users as $currUser) {
335            if ($obj->getAccessMode($currUser) >= $minMode)
336                array_push($newArr, $currUser);
337        }
338        return $newArr;
339    } /* }}} */
340
341    /**
342     * Filter out document links which can not be accessed by a given user
343     *
344     * Returns a filtered list of links which are accessible by the
345     * given user. A link is only accessible, if it is publically visible,
346     * owned by the user, or the accessing user is an administrator.
347     *
348     * @param SeedDMS_Core_DocumentLink[] $links list of objects of type SeedDMS_Core_DocumentLink
349     * @param object $user user for which access is being checked
350     * @param string $access set if source or target of link shall be checked
351     * for sufficient access rights. Set to 'source' if the source document
352     * of a link is to be checked, set to 'target' for the target document.
353     * If not set, then access right aren't checked at all.
354     * @return array filtered list of links
355     */
356    static function filterDocumentLinks($user, $links, $access='') { /* {{{ */
357        $tmp = array();
358        foreach ($links as $link) {
359            if ($link->isPublic() || ($link->getUser()->getID() == $user->getID()) || $user->isAdmin()){
360                if($access == 'source') {
361                    $obj = $link->getDocument();
362                    if ($obj->getAccessMode($user) >= M_READ)
363                        array_push($tmp, $link);
364                } elseif($access == 'target') {
365                    $obj = $link->getTarget();
366                    if ($obj->getAccessMode($user) >= M_READ)
367                        array_push($tmp, $link);
368                } else {
369                    array_push($tmp, $link);
370                }
371            }
372        }
373        return $tmp;
374    } /* }}} */
375
376    /**
377     * Merge access lists
378     *
379     * Merges two access lists. Objects of the second list will override objects
380     * in the first list.
381     *
382     * @param array $first list of access rights as returned by
383     * SeedDMS_Core_Document:: getAccessList() or SeedDMS_Core_Folder::getAccessList()
384     * @param array $secont list of access rights
385     * @return array merged list
386     */
387    static function mergeAccessLists($first, $second) { /* {{{ */
388        if($first && !$second)
389            return $first;
390        if(!$first && $second)
391            return $second;
392
393        $tmp = array('users'=>array(), 'groups'=>array());
394        if(!isset($first['users']) || !isset($first['groups']) ||
395            !isset($second['users']) || !isset($second['groups']))
396            return false;
397
398        foreach ($first['users'] as $f) {
399            $new = $f;
400            foreach ($second['users'] as $i=>$s) {
401                if($f->getUserID() == $s->getUserID()) {
402                    $new = $s;
403                    unset($second['users'][$i]);
404                    break;
405                }
406            }
407            array_push($tmp['users'], $new);
408        }
409        foreach ($seconf['users'] as $f) {
410            array_push($tmp['users'], $f);
411        }
412
413        foreach ($first['groups'] as $f) {
414            $new = $f;
415            foreach ($second['groups'] as $i=>$s) {
416                if($f->getGroupID() == $s->getGroupID()) {
417                    $new = $s;
418                    unset($second['groups'][$i]);
419                    break;
420                }
421            }
422            array_push($tmp['groups'], $new);
423        }
424        foreach ($second['groups'] as $f) {
425            array_push($tmp['groups'], $f);
426        }
427
428        return $tmp;
429    } /* }}} */
430
431    /*
432     * Filter out document attachments which can not be accessed by a given user
433     *
434     * Returns a filtered list of files which are accessible by the
435     * given user. A file is only accessible, if it is publically visible,
436     * owned by the user, or the accessing user is an administrator.
437     *
438     * @param array $files list of objects of type SeedDMS_Core_DocumentFile
439     * @param object $user user for which access is being checked
440     * @return array filtered list of files
441     */
442    static function filterDocumentFiles($user, $files) { /* {{{ */
443        $tmp = array();
444        if($files) {
445            foreach ($files as $file)
446                if ($file->isPublic() || ($file->getUser()->getID() == $user->getID()) || $user->isAdmin() || ($file->getDocument()->getOwner()->getID() == $user->getID()))
447                    array_push($tmp, $file);
448        }
449        return $tmp;
450    } /* }}} */
451
452    /** @noinspection PhpUndefinedClassInspection */
453    /**
454     * Create a new instance of the dms
455     *
456     * @param SeedDMS_Core_DatabaseAccess $db object of class {@link SeedDMS_Core_DatabaseAccess}
457     *        to access the underlying database
458     * @param string $contentDir path in filesystem containing the data store
459     *        all document contents is stored
460     */
461    function __construct($db, $contentDir) { /* {{{ */
462        $this->db = $db;
463        if(substr($contentDir, -1) == '/')
464            $this->contentDir = $contentDir;
465        else
466            $this->contentDir = $contentDir.'/';
467        $this->rootFolderID = 1;
468        $this->user = null;
469        $this->maxDirID = 0; //31998;
470        $this->forceRename = false;
471        $this->forceLink = false;
472        $this->checkWithinRootDir = false;
473        $this->noReadForStatus = array();
474        $this->user = null;
475        $this->classnames = array();
476        $this->classnames['folder'] = 'SeedDMS_Core_Folder';
477        $this->classnames['document'] = 'SeedDMS_Core_Document';
478        $this->classnames['documentcontent'] = 'SeedDMS_Core_DocumentContent';
479        $this->classnames['documentfile'] = 'SeedDMS_Core_DocumentFile';
480        $this->classnames['user'] = 'SeedDMS_Core_User';
481        $this->classnames['role'] = 'SeedDMS_Core_Role';
482        $this->classnames['group'] = 'SeedDMS_Core_Group';
483        $this->classnames['transmittal'] = 'SeedDMS_Core_Transmittal';
484        $this->classnames['transmittalitem'] = 'SeedDMS_Core_TransmittalItem';
485        $this->usecache = false;
486        $this->cache['users'] = [];
487        $this->callbacks = array();
488        $this->lasterror = '';
489        $this->version = '@package_version@';
490        if($this->version[0] == '@')
491            $this->version = '6.0.33';
492    } /* }}} */
493
494    /**
495     * Return class name of classes instanciated by SeedDMS_Core
496     *
497     * This method returns the class name of those objects being instantiated
498     * by the dms. Each class has an internal place holder, which must be
499     * passed to function.
500     *
501     * @param string $objectname placeholder (can be one of 'folder', 'document',
502     * 'documentcontent', 'user', 'group')
503     *
504     * @return string/boolean name of class or false if object name is invalid
505     */
506    function getClassname($objectname) { /* {{{ */
507        if(isset($this->classnames[$objectname]))
508            return $this->classnames[$objectname];
509        else
510            return false;
511    } /* }}} */
512
513    /**
514     * Set class name of instantiated objects
515     *
516     * This method sets the class name of those objects being instatiated
517     * by the dms. It is mainly used to create a new class (possible
518     * inherited from one of the available classes) implementing new
519     * features. The method should be called in the postInitDMS hook.
520     *
521     * @param string $objectname placeholder (can be one of 'folder', 'document',
522     * 'documentcontent', 'user', 'group'
523     * @param string $classname name of class
524     *
525     * @return string/boolean name of old class or false if not set
526     */
527    function setClassname($objectname, $classname) { /* {{{ */
528        if(isset($this->classnames[$objectname]))
529            $oldclass =  $this->classnames[$objectname];
530        else
531            $oldclass = false;
532        $this->classnames[$objectname] = $classname;
533        return $oldclass;
534    } /* }}} */
535
536    /**
537     * Return list of decorators
538     *
539     * This method returns the list of decorator class names of those objects
540     * being instantiated
541     * by the dms. Each class has an internal place holder, which must be
542     * passed to function.
543     *
544     * @param string $objectname placeholder (can be one of 'folder', 'document',
545     * 'documentcontent', 'user', 'group')
546     *
547     * @return array/boolean list of class names or false if object name is invalid
548     */
549    function getDecorators($objectname) { /* {{{ */
550        if(isset($this->decorators[$objectname]))
551            return $this->decorators[$objectname];
552        else
553            return false;
554    } /* }}} */
555
556    /**
557     * Add a decorator
558     *
559     * This method adds a single decorator class name to the list of decorators
560     * of those objects being instantiated
561     * by the dms. Each class has an internal place holder, which must be
562     * passed to function.
563     *
564     * @param string $objectname placeholder (can be one of 'folder', 'document',
565     * 'documentcontent', 'user', 'group')
566     *
567     * @return boolean true if decorator could be added, otherwise false
568     */
569    function addDecorator($objectname, $decorator) { /* {{{ */
570        $this->decorators[$objectname][] = $decorator;
571        return true;
572    } /* }}} */
573
574    /**
575     * Return database where meta data is stored
576     *
577     * This method returns the database object as it was set by the first
578     * parameter of the constructor.
579     *
580     * @return SeedDMS_Core_DatabaseAccess database
581     */
582    function getDB() { /* {{{ */
583        return $this->db;
584    } /* }}} */
585
586    /**
587     * Return the database version
588     *
589     * @return array|bool
590     */
591    function getDBVersion() { /* {{{ */
592        $tbllist = $this->db->TableList();
593        $tbllist = explode(',',strtolower(join(',',$tbllist)));
594        if(!in_array('tblversion', $tbllist))
595            return false;
596        $queryStr = "SELECT * FROM `tblVersion` ORDER BY `major`,`minor`,`subminor` LIMIT 1";
597        $resArr = $this->db->getResultArray($queryStr);
598        if (is_bool($resArr) && $resArr == false)
599            return false;
600        if (count($resArr) != 1)
601            return false;
602        $resArr = $resArr[0];
603        return $resArr;
604    } /* }}} */
605
606    /**
607     * Check if the version in the database is the same as of this package
608     * Only the major and minor version number will be checked.
609     *
610     * @return boolean returns false if versions do not match, but returns
611     *         true if version matches or table tblVersion does not exists.
612     */
613    function checkVersion() { /* {{{ */
614        $tbllist = $this->db->TableList();
615        $tbllist = explode(',',strtolower(join(',',$tbllist)));
616        if(!in_array('tblversion', $tbllist))
617            return true;
618        $queryStr = "SELECT * FROM `tblVersion` ORDER BY `major`,`minor`,`subminor` LIMIT 1";
619        $resArr = $this->db->getResultArray($queryStr);
620        if (is_bool($resArr) && $resArr == false)
621            return false;
622        if (count($resArr) != 1)
623            return false;
624        $resArr = $resArr[0];
625        $ver = explode('.', $this->version);
626        if(($resArr['major'] != $ver[0]) || ($resArr['minor'] != $ver[1]))
627            return false;
628        return true;
629    } /* }}} */
630
631    /**
632     * Set id of root folder
633     *
634     * This function must be called right after creating an instance of
635     * {@link SeedDMS_Core_DMS}
636     *
637     * The new root folder id will only be set if the folder actually
638     * exists. In that case the old root folder id will be returned.
639     * If it does not exists, the method will return false;
640     * @param integer $id id of root folder
641     * @return boolean/int old root folder id if new root folder exists, otherwise false
642     */
643    function setRootFolderID($id) { /* {{{ */
644        if($this->getFolder($id)) {
645            $oldid = $this->rootFolderID;
646            $this->rootFolderID = $id;
647            return $oldid;
648        }
649        return false;
650    } /* }}} */
651
652    /**
653     * Set maximum number of subdirectories per directory
654     *
655     * The value of maxDirID is quite crucial, because each document is
656     * stored within a directory in the filesystem. Consequently, there can be
657     * a maximum number of documents, because depending on the file system
658     * the maximum number of subdirectories is limited. Since version 3.3.0 of
659     * SeedDMS an additional directory level has been introduced, which
660     * will be created when maxDirID is not 0. All documents
661     * from 1 to maxDirID-1 will be saved in 1/<docid>, documents from maxDirID
662     * to 2*maxDirID-1 are stored in 2/<docid> and so on.
663     *
664     * Modern file systems like ext4 do not have any restrictions on the number
665     * of subdirectories anymore. Therefore it is best if this parameter is
666     * set to 0. Never change this parameter if documents has already been
667     * created.
668     *
669     * This function must be called right after creating an instance of
670     * {@link SeedDMS_Core_DMS}
671     *
672     * @param integer $id id of root folder
673     */
674    function setMaxDirID($id) { /* {{{ */
675        $this->maxDirID = $id;
676    } /* }}} */
677
678    /**
679     * Get root folder
680     *
681     * @return SeedDMS_Core_Folder|boolean return the object of the root folder or false if
682     *        the root folder id was not set before with {@link setRootFolderID}.
683     */
684    function getRootFolder() { /* {{{ */
685        if(!$this->rootFolderID) return false;
686        return $this->getFolder($this->rootFolderID);
687    } /* }}} */
688
689    function setForceRename($enable) { /* {{{ */
690        $this->forceRename = $enable;
691    } /* }}} */
692
693    function setForceLink($enable) { /* {{{ */
694        $this->forceLink = $enable;
695    } /* }}} */
696
697    /**
698     * Set the logged in user
699     *
700     * This method tells SeeDMS_Core_DMS the currently logged in user. It must be
701     * called right after instanciating the class, because some methods in
702     * SeedDMS_Core_Document() require the currently logged in user.
703     *
704     * @param object $user this muss not be empty and an instance of SeedDMS_Core_User
705     * @return bool|object returns the old user object or null on success, otherwise false
706     *
707     */
708    function setUser($user) { /* {{{ */
709        if(!$user) {
710            $olduser = $this->user;
711            $this->user = null;
712            return $olduser;
713        }
714        if(is_object($user) && (get_class($user) == $this->getClassname('user'))) {
715            $olduser = $this->user;
716            $this->user = $user;
717            return $olduser;
718        }
719        return false;
720    } /* }}} */
721
722    /**
723     * Get the logged in user
724     *
725     * Returns the currently logged in user, as set by setUser()
726     *
727     * @return SeedDMS_Core_User $user
728     *
729     */
730    function getLoggedInUser() { /* {{{ */
731        return $this->user;
732    } /* }}} */
733
734    /**
735     * Return a document by its id
736     *
737     * This function retrieves a document from the database by its id.
738     *
739     * @param integer $id internal id of document
740     * @return SeedDMS_Core_Document instance of {@link SeedDMS_Core_Document}, null or false
741     */
742    function getDocument($id) { /* {{{ */
743        $classname = $this->classnames['document'];
744        return $classname::getInstance($id, $this);
745    } /* }}} */
746
747    /**
748     * Returns all documents of a given user
749     *
750     * @param object $user
751     * @return array list of documents
752     */
753    function getDocumentsByUser($user) { /* {{{ */
754        return $user->getDocuments();
755    } /* }}} */
756
757    /**
758     * Returns all documents locked by a given user
759     *
760     * @param object $user
761     * @return array list of documents
762     */
763    function getDocumentsLockedByUser($user) { /* {{{ */
764        return $user->getDocumentsLocked();
765    } /* }}} */
766
767    /**
768     * Returns all documents which already expired or will expire in the future
769     *
770     * The parameter $date will be relative to the start of the day. It can
771     * be either a number of days (if an integer is passed) or a date string
772     * in the format 'YYYY-MM-DD'.
773     * If the parameter $date is a negative number or a date in the past, then
774     * all documents from the start of that date till the end of the current
775     * day will be returned. If $date is a positive integer or $date is a
776     * date in the future, then all documents from the start of the current
777     * day till the end of the day of the given date will be returned.
778     * Passing 0 or the
779     * current date in $date, will return all documents expiring the current
780     * day.
781     * @param string $date date in format YYYY-MM-DD or an integer with the number
782     *   of days. A negative value will cover the days in the past.
783     * @param SeedDMS_Core_User $user limits the documents on those owned
784     *   by this user
785     * @param string $orderby n=name, e=expired
786     * @param string $orderdir d=desc or a=asc
787     * @param bool $update update status of document if set to true
788     * @return bool|SeedDMS_Core_Document[]
789     */
790    function getDocumentsExpired($date, $user=null, $orderby='e', $orderdir='desc', $update=true) { /* {{{ */
791        $db = $this->getDB();
792
793        if (!$db->createTemporaryTable("ttstatid") || !$db->createTemporaryTable("ttcontentid")) {
794            return false;
795        }
796
797        $tsnow = mktime(0, 0, 0); /* Start of today */
798        if(is_int($date)) {
799            $ts = $tsnow + $date * 86400;
800        } elseif(is_string($date)) {
801            $tmp = explode('-', $date, 3);
802            if(count($tmp) != 3)
803                return false;
804            if(!self::checkDate($date, 'Y-m-d'))
805                return false;
806            $ts = mktime(0, 0, 0, (int) $tmp[1], (int) $tmp[2], (int) $tmp[0]);
807        } else
808            return false;
809
810        if($ts < $tsnow) { /* Check for docs expired in the past */
811            $startts = $ts;
812            $endts = $tsnow+86400; /* Use end of day */
813            $updatestatus = $update;
814        } else { /* Check for docs which will expire in the future */
815            $startts = $tsnow;
816            $endts = $ts+86400; /* Use end of day */
817            $updatestatus = false;
818        }
819
820        /* Get all documents which have an expiration date. It doesn't check for
821         * the latest status which should be S_EXPIRED, but doesn't have to, because
822         * status may have not been updated after the expiration date has been reached.
823         **/
824        $queryStr = "SELECT `tblDocuments`.`id`, `tblDocumentStatusLog`.`status`  FROM `tblDocuments` ".
825            "LEFT JOIN `ttcontentid` ON `ttcontentid`.`document` = `tblDocuments`.`id` ".
826            "LEFT JOIN `tblDocumentContent` ON `tblDocuments`.`id` = `tblDocumentContent`.`document` AND `tblDocumentContent`.`version` = `ttcontentid`.`maxVersion` ".
827            "LEFT JOIN `tblDocumentStatus` ON `tblDocumentStatus`.`documentID` = `tblDocumentContent`.`document` AND `tblDocumentContent`.`version` = `tblDocumentStatus`.`version` ".
828            "LEFT JOIN `ttstatid` ON `ttstatid`.`statusID` = `tblDocumentStatus`.`statusID` ".
829            "LEFT JOIN `tblDocumentStatusLog` ON `tblDocumentStatusLog`.`statusLogID` = `ttstatid`.`maxLogID`";
830        $queryStr .= 
831            " WHERE `tblDocuments`.`expires` >= ".$startts." AND `tblDocuments`.`expires` < ".$endts;
832        if($user)
833            $queryStr .=
834                " AND `tblDocuments`.`owner` = '".$user->getID()."' ";
835        $queryStr .= 
836            " ORDER BY ".($orderby == 'e' ? "`expires`" : "`name`")." ".($orderdir == 'd' ? "DESC" : "ASC");
837
838        $resArr = $db->getResultArray($queryStr);
839        if (is_bool($resArr) && !$resArr)
840            return false;
841
842        /** @var SeedDMS_Core_Document[] $documents */
843        $documents = array();
844        foreach ($resArr as $row) {
845            $document = $this->getDocument($row["id"]);
846            if($updatestatus) {
847                $document->verifyLastestContentExpriry();
848            }
849            $documents[] = $document;
850        }
851        return $documents;
852    } /* }}} */
853
854    /**
855     * Returns a document by its name
856     *
857     * This function searches a document by its name and restricts the search
858     * to the given folder if passed as the second parameter.
859     * If there are more than one document with that name, then only the
860     * one with the highest id will be returned. 
861     *
862     * @param string $name Name of the document
863     * @param object $folder parent folder of document
864     * @return SeedDMS_Core_Document|null|boolean found document or null if not document was found or false in case of an error
865     */
866    function getDocumentByName($name, $folder=null) { /* {{{ */
867        $name = trim($name);
868        if (!$name) return false;
869
870        $queryStr = "SELECT `tblDocuments`.*, `tblDocumentLocks`.`userID` as `lockUser` ".
871            "FROM `tblDocuments` ".
872            "LEFT JOIN `tblDocumentLocks` ON `tblDocuments`.`id`=`tblDocumentLocks`.`document` ".
873            "WHERE `tblDocuments`.`name` = " . $this->db->qstr($name);
874        if($folder)
875            $queryStr .= " AND `tblDocuments`.`folder` = ". $folder->getID();
876        if($this->checkWithinRootDir)
877            $queryStr .= " AND `tblDocuments`.`folderList` LIKE '%:".$this->rootFolderID.":%'";
878        $queryStr .= " ORDER BY `tblDocuments`.`id` DESC LIMIT 1";
879
880        $resArr = $this->db->getResultArray($queryStr);
881        if (is_bool($resArr) && !$resArr)
882            return false;
883
884        if(!$resArr)
885            return null;
886
887        $row = $resArr[0];
888        /** @var SeedDMS_Core_Document $document */
889        $document = new $this->classnames['document']($row["id"], $row["name"], $row["comment"], $row["date"], $row["expires"], $row["owner"], $row["folder"], $row["inheritAccess"], $row["defaultAccess"], $row["lockUser"], $row["keywords"], $row["sequence"]);
890        $document->setDMS($this);
891        return $document;
892    } /* }}} */
893
894    /**
895     * Returns a document by the original file name of the last version
896     *
897     * This function searches a document by the name of the last document
898     * version and restricts the search
899     * to given folder if passed as the second parameter.
900     * If there are more than one document with that name, then only the
901     * one with the highest id will be returned. 
902     *
903     * @param string $name Name of the original file
904     * @param object $folder parent folder of document
905     * @return SeedDMS_Core_Document|null|boolean found document or null if not document was found or false in case of an error
906     */
907    function getDocumentByOriginalFilename($name, $folder=null) { /* {{{ */
908        $name = trim($name);
909        if (!$name) return false;
910
911        if (!$this->db->createTemporaryTable("ttcontentid")) {
912            return false;
913        }
914        $queryStr = "SELECT `tblDocuments`.*, `tblDocumentLocks`.`userID` as `lockUser` ".
915            "FROM `tblDocuments` ".
916            "LEFT JOIN `ttcontentid` ON `ttcontentid`.`document` = `tblDocuments`.`id` ".
917            "LEFT JOIN `tblDocumentContent` ON `tblDocumentContent`.`document` = `tblDocuments`.`id` AND `tblDocumentContent`.`version` = `ttcontentid`.`maxVersion` ".
918            "LEFT JOIN `tblDocumentLocks` ON `tblDocuments`.`id`=`tblDocumentLocks`.`document` ".
919            "WHERE `tblDocumentContent`.`orgFileName` = " . $this->db->qstr($name);
920        if($folder)
921            $queryStr .= " AND `tblDocuments`.`folder` = ". $folder->getID();
922        $queryStr .= " ORDER BY `tblDocuments`.`id` DESC LIMIT 1";
923
924        $resArr = $this->db->getResultArray($queryStr);
925        if (is_bool($resArr) && !$resArr)
926            return false;
927
928        if(!$resArr)
929            return null;
930
931        $row = $resArr[0];
932        /** @var SeedDMS_Core_Document $document */
933        $document = new $this->classnames['document']($row["id"], $row["name"], $row["comment"], $row["date"], $row["expires"], $row["owner"], $row["folder"], $row["inheritAccess"], $row["defaultAccess"], $row["lockUser"], $row["keywords"], $row["sequence"]);
934        $document->setDMS($this);
935        return $document;
936    } /* }}} */
937
938    /**
939     * Return a document content by its id
940     *
941     * This function retrieves a document content from the database by its id.
942     *
943     * @param integer $id internal id of document content
944     * @return bool|null|SeedDMS_Core_DocumentContent found document content or null if not document content was found or false in case of an error
945
946     */
947    function getDocumentContent($id) { /* {{{ */
948        $classname = $this->classnames['documentcontent'];
949        return $classname::getInstance($id, $this);
950    } /* }}} */
951
952    /**
953     * Returns all documents with a predefined search criteria
954     *
955     * @param string $listtype type of document list, can be 'AppRevByMe',
956     * 'AppRevOwner', 'ReceiptByMe', 'ReviseByMe', 'LockedByMe', 'MyDocs'
957     * @param object $user user
958     * @return array list of documents records
959     */
960    function countTasks($listtype, $user=null, $param5=true) { /* {{{ */
961        if (!$this->db->createTemporaryTable("ttstatid") || !$this->db->createTemporaryTable("ttcontentid")) {
962            return false;
963        }
964        $groups = array();
965        if($user) {
966            $tmp = $user->getGroups();
967            foreach($tmp as $group)
968                $groups[] = $group->getID();
969        }
970        $selectStr = "count(distinct ttcontentid.document) c ";
971        $queryStr = 
972            "FROM `ttcontentid` ".
973            "LEFT JOIN `tblDocumentStatus` ON `tblDocumentStatus`.`documentID`=`ttcontentid`.`document` AND `tblDocumentStatus`.`version`=`ttcontentid`.`maxVersion` ".
974            "LEFT JOIN `ttstatid` ON `ttstatid`.`statusID` = `tblDocumentStatus`.`statusID` ".
975            "LEFT JOIN `tblDocumentStatusLog` ON `ttstatid`.`statusID` = `tblDocumentStatusLog`.`statusID` AND `ttstatid`.`maxLogID` = `tblDocumentStatusLog`.`statusLogID` ";
976        switch($listtype) {
977        case 'ReviewByMe': // Documents I have to review {{{
978            if (!$this->db->createTemporaryTable("ttreviewid")) {
979                return false;
980            }
981            $queryStr .=
982                "LEFT JOIN `tblDocumentReviewers` on `ttcontentid`.`document`=`tblDocumentReviewers`.`documentID` AND `ttcontentid`.`maxVersion`=`tblDocumentReviewers`.`version` ".
983                "LEFT JOIN `ttreviewid` ON `ttreviewid`.`reviewID` = `tblDocumentReviewers`.`reviewID` ".
984                "LEFT JOIN `tblDocumentReviewLog` ON `tblDocumentReviewLog`.`reviewLogID`=`ttreviewid`.`maxLogID` ";
985
986            $queryStr .= "WHERE (`tblDocumentReviewers`.`type` = 0 AND `tblDocumentReviewers`.`required` = ".$user->getID()." ";
987            if($groups)
988                $queryStr .= "OR `tblDocumentReviewers`.`type` = 1 AND `tblDocumentReviewers`.`required` IN (".implode(',', $groups).") ";
989            $queryStr .= ") ";
990            $queryStr .= "AND `tblDocumentReviewLog`.`status` = 0 ";
991            $docstatarr = array(S_DRAFT_REV);
992            if($param5)
993                $docstatarr[] = S_EXPIRED;
994            $queryStr .= "AND `tblDocumentStatusLog`.`status` IN (".implode(',', $docstatarr).") ";
995            break; /* }}} */
996        case 'ApproveByMe': // Documents I have to approve {{{
997            if (!$this->db->createTemporaryTable("ttapproveid")) {
998                return false;
999            }
1000            $queryStr .=
1001                "LEFT JOIN `tblDocumentApprovers` on `ttcontentid`.`document`=`tblDocumentApprovers`.`documentID` AND `ttcontentid`.`maxVersion`=`tblDocumentApprovers`.`version` ".
1002                "LEFT JOIN `ttapproveid` ON `ttapproveid`.`approveID` = `tblDocumentApprovers`.`approveID` ".
1003                "LEFT JOIN `tblDocumentApproveLog` ON `tblDocumentApproveLog`.`approveLogID`=`ttapproveid`.`maxLogID` ";
1004
1005            if($user) {
1006                $queryStr .= "WHERE (`tblDocumentApprovers`.`type` = 0 AND `tblDocumentApprovers`.`required` = ".$user->getID()." ";
1007                if($groups)
1008                    $queryStr .= "OR `tblDocumentApprovers`.`type` = 1 AND `tblDocumentApprovers`.`required` IN (".implode(',', $groups).") ";
1009                $queryStr .= ") ";
1010            }
1011            $queryStr .= "AND `tblDocumentApproveLog`.`status` = 0 ";
1012            $docstatarr = array(S_DRAFT_APP);
1013            if($param5)
1014                $docstatarr[] = S_EXPIRED;
1015            $queryStr .= "AND `tblDocumentStatusLog`.`status` IN (".implode(',', $docstatarr).") ";
1016            break; /* }}} */
1017        case 'ReceiptByMe': // Documents I have to receipt {{{
1018            if (!$this->db->createTemporaryTable("ttreceiptid")) {
1019                return false;
1020            }
1021            $queryStr .=
1022                "LEFT JOIN `tblDocumentRecipients` on `ttcontentid`.`document`=`tblDocumentRecipients`.`documentID` AND `ttcontentid`.`maxVersion`=`tblDocumentRecipients`.`version` ".
1023                "LEFT JOIN `ttreceiptid` ON `ttreceiptid`.`receiptID` = `tblDocumentRecipients`.`receiptID` ".
1024                "LEFT JOIN `tblDocumentReceiptLog` ON `tblDocumentReceiptLog`.`receiptLogID`=`ttreceiptid`.`maxLogID` ";
1025
1026            if($user) {
1027                $queryStr .= "WHERE (`tblDocumentRecipients`.`type` = 0 AND `tblDocumentRecipients`.`required` = ".$user->getID()." ";
1028                if($groups)
1029                    $queryStr .= "OR `tblDocumentRecipients`.`type` = 1 AND `tblDocumentRecipients`.`required` IN (".implode(',', $groups).") ";
1030                $queryStr .= ") ";
1031            }
1032            $queryStr .= "AND `tblDocumentReceiptLog`.`status` = 0 ";
1033            $queryStr .= "AND `tblDocumentStatusLog`.`status` IN (".S_RELEASED.") ";
1034            break; /* }}} */
1035        case 'ReviseByMe': // Documents I have to receipt {{{
1036            if (!$this->db->createTemporaryTable("ttrevisionid")) {
1037                return false;
1038            }
1039            $queryStr .=
1040                "LEFT JOIN `tblDocumentRevisors` on `ttcontentid`.`document`=`tblDocumentRevisors`.`documentID` AND `ttcontentid`.`maxVersion`=`tblDocumentRevisors`.`version` ".
1041                "LEFT JOIN `ttrevisionid` ON `ttrevisionid`.`revisionID` = `tblDocumentRevisors`.`revisionID` ".
1042                "LEFT JOIN `tblDocumentRevisionLog` ON `tblDocumentRevisionLog`.`revisionLogID`=`ttrevisionid`.`maxLogID` ";
1043
1044            if($user) {
1045                $queryStr .= "WHERE (`tblDocumentRevisors`.`type` = 0 AND `tblDocumentRevisors`.`required` = ".$user->getID()." ";
1046                if($groups)
1047                    $queryStr .= "OR `tblDocumentRevisors`.`type` = 1 AND `tblDocumentRevisors`.`required` IN (".implode(',', $groups).") ";
1048                $queryStr .= ") ";
1049            }
1050            $queryStr .= "AND `tblDocumentRevisionLog`.`status` = 0 ";
1051            $queryStr .= "AND `tblDocumentStatusLog`.`status` IN (".S_IN_REVISION.") ";
1052            break; /* }}} */
1053        case 'SleepingReviseByMe': // Documents I have to receipt {{{
1054            if (!$this->db->createTemporaryTable("ttrevisionid")) {
1055                return false;
1056            }
1057            $queryStr .=
1058                "LEFT JOIN `tblDocumentRevisors` on `ttcontentid`.`document`=`tblDocumentRevisors`.`documentID` AND `ttcontentid`.`maxVersion`=`tblDocumentRevisors`.`version` ".
1059                "LEFT JOIN `ttrevisionid` ON `ttrevisionid`.`revisionID` = `tblDocumentRevisors`.`revisionID` ".
1060                "LEFT JOIN `tblDocumentRevisionLog` ON `tblDocumentRevisionLog`.`revisionLogID`=`ttrevisionid`.`maxLogID` ";
1061
1062            if($user) {
1063                $queryStr .= "WHERE (`tblDocumentRevisors`.`type` = 0 AND `tblDocumentRevisors`.`required` = ".$user->getID()." ";
1064                if($groups)
1065                    $queryStr .= "OR `tblDocumentRevisors`.`type` = 1 AND `tblDocumentRevisors`.`required` IN (".implode(',', $groups).") ";
1066                $queryStr .= ") ";
1067            }
1068            $queryStr .= "AND `tblDocumentContent`.`revisiondate` IS NOT NULL AND `tblDocumentContent`.`revisiondate` <= ".$this->db->getCurrentDatetime(14)." ";
1069            $queryStr .= "AND `tblDocumentRevisionLog`.`status` = -3 ";
1070            $queryStr .= "AND `tblDocumentStatusLog`.`status` IN (".S_RELEASED.") ";
1071            break; /* }}} */
1072        case 'NeedsCorrectionOwner': // Documents that need to be corrected {{{
1073            $queryStr .=
1074                "LEFT JOIN `tblDocuments` ON `tblDocuments`.`id` = `ttcontentid`.`document` ";
1075            $queryStr .= "WHERE `tblDocuments`.`owner` = '".$user->getID()."' ".
1076                "AND `tblDocumentStatusLog`.`status` IN (".S_NEEDS_CORRECTION.") ";
1077            break; /* }}} */
1078        case 'WorkflowByMe': // Documents which need my workflow action {{{
1079
1080            $queryStr .=
1081                "LEFT JOIN `tblWorkflowDocumentContent` on `ttcontentid`.`document`=`tblWorkflowDocumentContent`.`document` AND `ttcontentid`.`maxVersion`=`tblWorkflowDocumentContent`.`version` ".
1082                "LEFT JOIN `tblWorkflowTransitions` on `tblWorkflowDocumentContent`.`workflow`=`tblWorkflowTransitions`.`workflow` AND `tblWorkflowDocumentContent`.`state`=`tblWorkflowTransitions`.`state` ".
1083                "LEFT JOIN `tblWorkflowTransitionUsers` on `tblWorkflowTransitionUsers`.`transition` = `tblWorkflowTransitions`.`id` ".
1084                "LEFT JOIN `tblWorkflowTransitionGroups` on `tblWorkflowTransitionGroups`.`transition` = `tblWorkflowTransitions`.`id` ";
1085
1086            if($user) {
1087                $queryStr .= "WHERE (`tblWorkflowTransitionUsers`.`userid` = ".$user->getID()." ";
1088                if($groups)
1089                    $queryStr .= "OR `tblWorkflowTransitionGroups`.`groupid` IN (".implode(',', $groups).")";
1090                $queryStr .= ") ";
1091            }
1092            $queryStr .= "AND `tblDocumentStatusLog`.`status` = ".S_IN_WORKFLOW." ";
1093            break; // }}}
1094        }
1095        if($queryStr) {
1096            $resArr = $this->db->getResultArray('SELECT '.$selectStr.$queryStr);
1097            if (is_bool($resArr) && !$resArr) {
1098                return false;
1099            }
1100        } else {
1101            return false;
1102        }
1103        return $resArr[0]['c'];
1104    } /* }}} */
1105
1106    /**
1107     * Returns all documents with a predefined search criteria
1108     *
1109     * The records return have the following elements
1110     *
1111     * From Table tblDocuments
1112     * [id] => id of document
1113     * [name] => name of document
1114     * [comment] => comment of document
1115     * [date] => timestamp of creation date of document
1116     * [expires] => timestamp of expiration date of document
1117     * [owner] => user id of owner
1118     * [folder] => id of parent folder
1119     * [folderList] => column separated list of folder ids, e.g. :1:41:
1120     * [inheritAccess] => 1 if access is inherited
1121     * [defaultAccess] => default access mode
1122     * [locked] => always -1 (TODO: is this field still used?)
1123     * [keywords] => keywords of document
1124     * [sequence] => sequence of document
1125     *
1126     * From Table tblDocumentLocks
1127     * [lockUser] => id of user locking the document
1128     *
1129     * From Table tblDocumentStatusLog
1130     * [version] => latest version of document
1131     * [statusID] => id of latest status log
1132     * [documentID] => id of document
1133     * [status] => current status of document
1134     * [statusComment] => comment of current status
1135     * [statusDate] => datetime when the status was entered, e.g. 2014-04-17 21:35:51
1136     * [userID] => id of user who has initiated the status change
1137     *
1138     * From Table tblUsers
1139     * [ownerName] => name of owner of document
1140     * [statusName] => name of user who has initiated the status change
1141     *
1142     * @param string $listtype type of document list, can be 'AppRevByMe',
1143     * 'AppRevOwner', 'ReceiptByMe', 'ReviseByMe', 'LockedByMe', 'MyDocs'
1144     * @param SeedDMS_Core_User $param1 user
1145     * @param bool|integer|string $param2 if set to true
1146     * 'ReviewByMe', 'ApproveByMe', 'AppRevByMe', 'ReviseByMe', 'ReceiptByMe'
1147     * will also return documents which the reviewer, approver, etc.
1148     * has already taken care of. If set to false only
1149     * untouched documents will be returned. In case of 'ExpiredOwner',
1150     * 'SleepingReviseByMe' this
1151     * parameter contains the number of days (a negative number is allowed)
1152     * relativ to the current date or a date in format 'yyyy-mm-dd'
1153     * (even in the past).
1154     * @param string $param3 sort list by this field
1155     * @param string $param4 order direction
1156     * @param bool $param5 set to false if expired documents shall not be considered
1157     * @return array|bool
1158     */
1159    function getDocumentList($listtype, $param1=null, $param2=false, $param3='', $param4='', $param5=true) { /* {{{ */
1160        /* The following query will get all documents and lots of additional
1161         * information. It requires the two temporary tables ttcontentid and
1162         * ttstatid.
1163         */
1164        if (!$this->db->createTemporaryTable("ttstatid") || !$this->db->createTemporaryTable("ttcontentid")) {
1165            return false;
1166        }
1167        /* The following statement retrieves the status of the last version of all
1168         * documents. It must be restricted by further where clauses.
1169         */
1170/*
1171        $queryStr = "SELECT `tblDocuments`.*, `tblDocumentLocks`.`userID` as `lockUser`, ".
1172            "`tblDocumentContent`.`version`, `tblDocumentStatus`.*, `tblDocumentStatusLog`.`status`, ".
1173            "`tblDocumentStatusLog`.`comment` AS `statusComment`, `tblDocumentStatusLog`.`date` as `statusDate`, ".
1174            "`tblDocumentStatusLog`.`userID`, `oTbl`.`fullName` AS `ownerName`, `sTbl`.`fullName` AS `statusName` ".
1175            "FROM `tblDocumentContent` ".
1176            "LEFT JOIN `tblDocuments` ON `tblDocuments`.`id` = `tblDocumentContent`.`document` ".
1177            "LEFT JOIN `tblDocumentStatus` ON `tblDocumentStatus`.`documentID` = `tblDocumentContent`.`document` ".
1178            "LEFT JOIN `tblDocumentStatusLog` ON `tblDocumentStatusLog`.`statusID` = `tblDocumentStatus`.`statusID` ".
1179            "LEFT JOIN `ttstatid` ON `ttstatid`.`maxLogID` = `tblDocumentStatusLog`.`statusLogID` ".
1180            "LEFT JOIN `ttcontentid` ON `ttcontentid`.`maxVersion` = `tblDocumentStatus`.`version` AND `ttcontentid`.`document` = `tblDocumentStatus`.`documentID` ".
1181            "LEFT JOIN `tblDocumentLocks` ON `tblDocuments`.`id`=`tblDocumentLocks`.`document` ".
1182            "LEFT JOIN `tblUsers` AS `oTbl` on `oTbl`.`id` = `tblDocuments`.`owner` ".
1183            "LEFT JOIN `tblUsers` AS `sTbl` on `sTbl`.`id` = `tblDocumentStatusLog`.`userID` ".
1184            "WHERE `ttstatid`.`maxLogID`=`tblDocumentStatusLog`.`statusLogID` ".
1185            "AND `ttcontentid`.`maxVersion` = `tblDocumentContent`.`version` ";
1186 */
1187        /* New sql statement which retrieves all documents, its latest version and
1188         * status, the owner and user initiating the latest status.
1189         * It doesn't need the where clause anymore. Hence the statement could be
1190         * extended with further left joins.
1191         */
1192        $selectStr = "`tblDocuments`.*, `tblDocumentLocks`.`userID` as `lockUser`, ".
1193            "`tblDocumentContent`.`version`, `tblDocumentStatus`.*, `tblDocumentStatusLog`.`status`, ".
1194            "`tblDocumentStatusLog`.`comment` AS `statusComment`, `tblDocumentStatusLog`.`date` as `statusDate`, ".
1195            "`tblDocumentStatusLog`.`userID`, `oTbl`.`fullName` AS `ownerName`, `sTbl`.`fullName` AS `statusName` ";
1196        $queryStr =
1197            "FROM `ttcontentid` ".
1198            "LEFT JOIN `tblDocuments` ON `tblDocuments`.`id` = `ttcontentid`.`document` ".
1199            "LEFT JOIN `tblDocumentContent` ON `tblDocumentContent`.`document` = `ttcontentid`.`document` AND `tblDocumentContent`.`version` = `ttcontentid`.`maxVersion` ".
1200            "LEFT JOIN `tblDocumentStatus` ON `tblDocumentStatus`.`documentID`=`ttcontentid`.`document` AND `tblDocumentStatus`.`version`=`ttcontentid`.`maxVersion` ".
1201            "LEFT JOIN `ttstatid` ON `ttstatid`.`statusID` = `tblDocumentStatus`.`statusID` ".
1202            "LEFT JOIN `tblDocumentStatusLog` ON `ttstatid`.`statusID` = `tblDocumentStatusLog`.`statusID` AND `ttstatid`.`maxLogID` = `tblDocumentStatusLog`.`statusLogID` ".
1203            "LEFT JOIN `tblDocumentLocks` ON `ttcontentid`.`document`=`tblDocumentLocks`.`document` ".
1204            "LEFT JOIN `tblUsers` `oTbl` ON `oTbl`.`id` = `tblDocuments`.`owner` ".
1205            "LEFT JOIN `tblUsers` `sTbl` ON `sTbl`.`id` = `tblDocumentStatusLog`.`userID` ";
1206
1207//        echo $queryStr;
1208
1209        switch($listtype) {
1210        case 'AppRevByMe': // Documents I have to review/approve {{{
1211            $queryStr .= "WHERE 1=1 ";
1212
1213            $user = $param1;
1214            // Get document list for the current user.
1215            $reviewStatus = $user->getReviewStatus();
1216            $approvalStatus = $user->getApprovalStatus();
1217
1218            // Create a comma separated list of all the documentIDs whose information is
1219            // required.
1220            // Take only those documents into account which hasn't be touched by the user
1221            $dList = array();
1222            foreach ($reviewStatus["indstatus"] as $st) {
1223                if (($st["status"]==0 || $param2) && !in_array($st["documentID"], $dList)) {
1224                    $dList[] = $st["documentID"];
1225                }
1226            }
1227            foreach ($reviewStatus["grpstatus"] as $st) {
1228                if (($st["status"]==0 || $param2) && !in_array($st["documentID"], $dList)) {
1229                    $dList[] = $st["documentID"];
1230                }
1231            }
1232            foreach ($approvalStatus["indstatus"] as $st) {
1233                if (($st["status"]==0 || $param2) && !in_array($st["documentID"], $dList)) {
1234                    $dList[] = $st["documentID"];
1235                }
1236            }
1237            foreach ($approvalStatus["grpstatus"] as $st) {
1238                if (($st["status"]==0 || $param2) && !in_array($st["documentID"], $dList)) {
1239                    $dList[] = $st["documentID"];
1240                }
1241            }
1242            $docCSV = "";
1243            foreach ($dList as $d) {
1244                $docCSV .= (strlen($docCSV)==0 ? "" : ", ")."'".$d."'";
1245            }
1246
1247            if (strlen($docCSV)>0) {
1248                $docstatarr = array(S_DRAFT_REV, S_DRAFT_APP);
1249                if($param5)
1250                    $docstatarr[] = S_EXPIRED;
1251                $queryStr .= "AND `tblDocumentStatusLog`.`status` IN (".implode(',', $docstatarr).") ".
1252                            "AND `tblDocuments`.`id` IN (" . $docCSV . ") ".
1253                            "ORDER BY `statusDate` DESC";
1254            } else {
1255                $queryStr = '';
1256            }
1257            break; // }}}
1258        case 'ReviewByMe': // Documents I have to review {{{
1259            if (!$this->db->createTemporaryTable("ttreviewid")) {
1260                return false;
1261            }
1262            $user = $param1;
1263            $orderby = $param3;
1264            if($param4 == 'desc')
1265                $orderdir = 'DESC';
1266            else
1267                $orderdir = 'ASC';
1268
1269            $groups = array();
1270            if($user) {
1271                $tmp = $user->getGroups();
1272                foreach($tmp as $group)
1273                    $groups[] = $group->getID();
1274            }
1275
1276            $selectStr .= ", `tblDocumentReviewLog`.`date` as `duedate` ";
1277            $queryStr .=
1278                "LEFT JOIN `tblDocumentReviewers` ON `ttcontentid`.`document`=`tblDocumentReviewers`.`documentID` AND `ttcontentid`.`maxVersion`=`tblDocumentReviewers`.`version` ".
1279                "LEFT JOIN `ttreviewid` ON `ttreviewid`.`reviewID` = `tblDocumentReviewers`.`reviewID` ".
1280                "LEFT JOIN `tblDocumentReviewLog` ON `tblDocumentReviewLog`.`reviewLogID`=`ttreviewid`.`maxLogID` ";
1281
1282            if(1) {
1283            if($user) {
1284                $queryStr .= "WHERE (`tblDocumentReviewers`.`type` = 0 AND `tblDocumentReviewers`.`required` = ".$user->getID()." ";
1285                if($groups)
1286                    $queryStr .= "OR `tblDocumentReviewers`.`type` = 1 AND `tblDocumentReviewers`.`required` IN (".implode(',', $groups).") ";
1287                $queryStr .= ") ";
1288            }
1289            $docstatarr = array(S_DRAFT_REV);
1290            if($param5)
1291                $docstatarr[] = S_EXPIRED;
1292            $queryStr .= "AND `tblDocumentStatusLog`.`status` IN (".implode(',', $docstatarr).") ";
1293            if(!$param2)
1294                $queryStr .= " AND `tblDocumentReviewLog`.`status` = 0 ";
1295            if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1296            else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1297            else if ($orderby=='s') $queryStr .= "ORDER BY `tblDocumentStatusLog`.`status`";
1298            else $queryStr .= "ORDER BY `name`";
1299            $queryStr .= " ".$orderdir;
1300            } else {
1301            $queryStr .= "WHERE 1=1 ";
1302
1303            // Get document list for the current user.
1304            $reviewStatus = $user->getReviewStatus();
1305
1306            // Create a comma separated list of all the documentIDs whose information is
1307            // required.
1308            // Take only those documents into account which hasn't be touched by the user
1309            // ($st["status"]==0)
1310            $dList = array();
1311            foreach ($reviewStatus["indstatus"] as $st) {
1312                if (($st["status"]==0 || $param2) && !in_array($st["documentID"], $dList)) {
1313                    $dList[] = $st["documentID"];
1314                }
1315            }
1316            foreach ($reviewStatus["grpstatus"] as $st) {
1317                if (($st["status"]==0 || $param2) && !in_array($st["documentID"], $dList)) {
1318                    $dList[] = $st["documentID"];
1319                }
1320            }
1321            $docCSV = "";
1322            foreach ($dList as $d) {
1323                $docCSV .= (strlen($docCSV)==0 ? "" : ", ")."'".$d."'";
1324            }
1325
1326            if (strlen($docCSV)>0) {
1327                $queryStr .= "AND `tblDocumentStatusLog`.`status` IN (".S_DRAFT_REV.", ".S_EXPIRED.") ".
1328                            "AND `tblDocuments`.`id` IN (" . $docCSV . ") ";
1329                //$queryStr .= "ORDER BY `statusDate` DESC";
1330                if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1331                else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1332                else if ($orderby=='s') $queryStr .= "ORDER BY `status`";
1333                else $queryStr .= "ORDER BY `name`";
1334                $queryStr .= " ".$orderdir;
1335            } else {
1336                $queryStr = '';
1337            }
1338            }
1339            break; // }}}
1340        case 'ApproveByMe': // Documents I have to approve {{{
1341            if (!$this->db->createTemporaryTable("ttapproveid")) {
1342                return false;
1343            }
1344            $user = $param1;
1345            $orderby = $param3;
1346            if($param4 == 'desc')
1347                $orderdir = 'DESC';
1348            else
1349                $orderdir = 'ASC';
1350
1351            $groups = array();
1352            if($user) {
1353                $tmp = $user->getGroups();
1354                foreach($tmp as $group)
1355                    $groups[] = $group->getID();
1356            }
1357
1358            $selectStr .= ", `tblDocumentApproveLog`.`date` as `duedate` ";
1359            $queryStr .=
1360                "LEFT JOIN `tblDocumentApprovers` ON `ttcontentid`.`document`=`tblDocumentApprovers`.`documentID` AND `ttcontentid`.`maxVersion`=`tblDocumentApprovers`.`version` ".
1361                "LEFT JOIN `ttapproveid` ON `ttapproveid`.`approveID` = `tblDocumentApprovers`.`approveID` ".
1362                "LEFT JOIN `tblDocumentApproveLog` ON `tblDocumentApproveLog`.`approveLogID`=`ttapproveid`.`maxLogID` ";
1363
1364            if(1) {
1365            if($user) {
1366            $queryStr .= "WHERE (`tblDocumentApprovers`.`type` = 0 AND `tblDocumentApprovers`.`required` = ".$user->getID()." ";
1367            if($groups)
1368                $queryStr .= "OR `tblDocumentApprovers`.`type` = 1 AND `tblDocumentApprovers`.`required` IN (".implode(',', $groups).")";
1369            $queryStr .= ") ";
1370            }
1371            $docstatarr = array(S_DRAFT_APP);
1372            if($param5)
1373                $docstatarr[] = S_EXPIRED;
1374            $queryStr .= "AND `tblDocumentStatusLog`.`status` IN (".implode(',', $docstatarr).") ";
1375            if(!$param2)
1376                $queryStr .= " AND `tblDocumentApproveLog`.`status` = 0 ";
1377            if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1378            else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1379            else if ($orderby=='s') $queryStr .= "ORDER BY `tblDocumentStatusLog`.`status`";
1380            else $queryStr .= "ORDER BY `name`";
1381            $queryStr .= " ".$orderdir;
1382            } else {
1383            $queryStr .= "WHERE 1=1 ";
1384
1385            // Get document list for the current user.
1386            $approvalStatus = $user->getApprovalStatus();
1387
1388            // Create a comma separated list of all the documentIDs whose information is
1389            // required.
1390            // Take only those documents into account which hasn't be touched by the user
1391            // ($st["status"]==0)
1392            $dList = array();
1393            foreach ($approvalStatus["indstatus"] as $st) {
1394                if (($st["status"]==0 || $param2) && !in_array($st["documentID"], $dList)) {
1395                    $dList[] = $st["documentID"];
1396                }
1397            }
1398            foreach ($approvalStatus["grpstatus"] as $st) {
1399                if (($st["status"]==0 || $param2) && !in_array($st["documentID"], $dList)) {
1400                    $dList[] = $st["documentID"];
1401                }
1402            }
1403            $docCSV = "";
1404            foreach ($dList as $d) {
1405                $docCSV .= (strlen($docCSV)==0 ? "" : ", ")."'".$d."'";
1406            }
1407
1408            if (strlen($docCSV)>0) {
1409                $docstatarr = array(S_DRAFT_APP);
1410                if($param5)
1411                    $docstatarr[] = S_EXPIRED;
1412                $queryStr .= "AND `tblDocumentStatusLog`.`status` IN (".implode(',', $docstatarr).") ".
1413                            "AND `tblDocuments`.`id` IN (" . $docCSV . ") ";
1414                //$queryStr .= "ORDER BY `statusDate` DESC";
1415                if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1416                else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1417                else if ($orderby=='s') $queryStr .= "ORDER BY `status`";
1418                else $queryStr .= "ORDER BY `name`";
1419                $queryStr .= " ".$orderdir;
1420            } else {
1421                $queryStr = '';
1422            }
1423            }
1424            break; // }}}
1425        case 'ReceiptByMe': // Documents I have to receipt {{{
1426            if (!$this->db->createTemporaryTable("ttreceiptid")) {
1427                return false;
1428            }
1429            $user = $param1;
1430            $orderby = $param3;
1431            if($param4 == 'desc')
1432                $orderdir = 'DESC';
1433            else
1434                $orderdir = 'ASC';
1435
1436            $groups = array();
1437            $tmp = $user->getGroups();
1438            foreach($tmp as $group)
1439                $groups[] = $group->getID();
1440
1441            $selectStr .= ", `tblDocumentReceiptLog`.`date` as `duedate` ";
1442            $queryStr .=
1443                "LEFT JOIN `tblDocumentRecipients` on `ttcontentid`.`document`=`tblDocumentRecipients`.`documentID` AND `ttcontentid`.`maxVersion`=`tblDocumentRecipients`.`version` ".
1444                "LEFT JOIN `ttreceiptid` ON `ttreceiptid`.`receiptID` = `tblDocumentRecipients`.`receiptID` ".
1445                "LEFT JOIN `tblDocumentReceiptLog` ON `tblDocumentReceiptLog`.`receiptLogID`=`ttreceiptid`.`maxLogID` ";
1446
1447            if(1) {
1448            $queryStr .= "WHERE (`tblDocumentRecipients`.`type` = 0 AND `tblDocumentRecipients`.`required` = ".$user->getID()." ";
1449            /* Checking for groups slows down the statement extremly on sqlite */
1450            if($groups)
1451                $queryStr .= "OR `tblDocumentRecipients`.`type` = 1 AND `tblDocumentRecipients`.`required` IN (".implode(',', $groups).")";
1452            $queryStr .= ") ";
1453            $queryStr .= "AND `tblDocumentStatusLog`.`status` = ".S_RELEASED." ";
1454            if(!$param2)
1455                $queryStr .= " AND `tblDocumentReceiptLog`.`status` = 0 ";
1456            if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1457            else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1458            else if ($orderby=='s') $queryStr .= "ORDER BY `tblDocumentStatusLog`.`status`";
1459            else $queryStr .= "ORDER BY `name`";
1460            $queryStr .= " ".$orderdir;
1461            } else {
1462            $queryStr .= "WHERE 1=1 ";
1463
1464            // Get document list for the current user.
1465            $receiptStatus = $user->getReceiptStatus();
1466
1467            // Create a comma separated list of all the documentIDs whose information is
1468            // required.
1469            // Take only those documents into account which hasn't be touched by the user
1470            // ($st["status"]==0)
1471            $dList = array();
1472            foreach ($receiptStatus["indstatus"] as $st) {
1473                if (($st["status"]==0 || $param2) && !in_array($st["documentID"], $dList)) {
1474                    $dList[] = $st["documentID"];
1475                }
1476            }
1477            foreach ($receiptStatus["grpstatus"] as $st) {
1478                if (($st["status"]==0 || $param2) && !in_array($st["documentID"], $dList)) {
1479                    $dList[] = $st["documentID"];
1480                }
1481            }
1482            $docCSV = "";
1483            foreach ($dList as $d) {
1484                $docCSV .= (strlen($docCSV)==0 ? "" : ", ")."'".$d."'";
1485            }
1486
1487            if (strlen($docCSV)>0) {
1488                $queryStr .= "AND `tblDocuments`.`id` IN (" . $docCSV . ") ";
1489//                $queryStr .= "ORDER BY `statusDate` DESC";
1490                if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1491                else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1492                else if ($orderby=='s') $queryStr .= "ORDER BY `status`";
1493                else $queryStr .= "ORDER BY `name`";
1494                $queryStr .= " ".$orderdir;
1495            } else {
1496                $queryStr = '';
1497            }
1498            }
1499            break; // }}}
1500        case 'ReviseByMe': // Documents I have to revise {{{
1501            if (!$this->db->createTemporaryTable("ttrevisionid")) {
1502                return false;
1503            }
1504            $user = $param1;
1505            $orderby = $param3;
1506            if($param4 == 'desc')
1507                $orderdir = 'DESC';
1508            else
1509                $orderdir = 'ASC';
1510
1511            $groups = array();
1512            $tmp = $user->getGroups();
1513            foreach($tmp as $group)
1514                $groups[] = $group->getID();
1515
1516            $selectStr .= ", `tblDocumentRevisionLog`.`date` as `duedate` ";
1517            $queryStr .=
1518                "LEFT JOIN `tblDocumentRevisors` on `ttcontentid`.`document`=`tblDocumentRevisors`.`documentID` AND `ttcontentid`.`maxVersion`=`tblDocumentRevisors`.`version` ".
1519                "LEFT JOIN `ttrevisionid` ON `ttrevisionid`.`revisionID` = `tblDocumentRevisors`.`revisionID` ".
1520                "LEFT JOIN `tblDocumentRevisionLog` ON `tblDocumentRevisionLog`.`revisionLogID`=`ttrevisionid`.`maxLogID` ";
1521
1522            if(1) {
1523            $queryStr .= "WHERE (`tblDocumentRevisors`.`type` = 0 AND `tblDocumentRevisors`.`required` = ".$user->getID()." ";
1524            if($groups)
1525                $queryStr .= "OR `tblDocumentRevisors`.`type` = 1 AND `tblDocumentRevisors`.`required` IN (".implode(',', $groups).")";
1526            $queryStr .= ") ";
1527            $queryStr .= "AND `tblDocumentStatusLog`.`status` = ".S_IN_REVISION." ";
1528            if(!$param2)
1529                $queryStr .= " AND `tblDocumentRevisionLog`.`status` = 0 ";
1530            if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1531            else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1532            else if ($orderby=='s') $queryStr .= "ORDER BY `tblDocumentStatusLog`.`status`";
1533            else $queryStr .= "ORDER BY `name`";
1534            $queryStr .= " ".$orderdir;
1535            } else {
1536            $queryStr .= "WHERE 1=1 ";
1537
1538            // Get document list for the current user.
1539            $revisionStatus = $user->getRevisionStatus();
1540
1541            // Create a comma separated list of all the documentIDs whose information is
1542            // required.
1543            $dList = array();
1544            foreach ($revisionStatus["indstatus"] as $st) {
1545                if (($st["status"]==0 || $param2) && !in_array($st["documentID"], $dList)) {
1546                    $dList[] = $st["documentID"];
1547                }
1548            }
1549            foreach ($revisionStatus["grpstatus"] as $st) {
1550                if (($st["status"]==0 || $param2) && !in_array($st["documentID"], $dList)) {
1551                    $dList[] = $st["documentID"];
1552                }
1553            }
1554            $docCSV = "";
1555            foreach ($dList as $d) {
1556                $docCSV .= (strlen($docCSV)==0 ? "" : ", ")."'".$d."'";
1557            }
1558
1559            if (strlen($docCSV)>0) {
1560                $queryStr .= "AND `tblDocuments`.`id` IN (" . $docCSV . ") ";
1561                //$queryStr .= "ORDER BY `statusDate` DESC";
1562                if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1563                else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1564                else if ($orderby=='s') $queryStr .= "ORDER BY `status`";
1565                else $queryStr .= "ORDER BY `name`";
1566                $queryStr .= " ".$orderdir;
1567            } else {
1568                $queryStr = '';
1569            }
1570            }
1571            break; // }}}
1572        case 'SleepingReviseByMe': // Documents I have to revise but are still sleeping {{{
1573            if (!$this->db->createTemporaryTable("ttrevisionid")) {
1574                return false;
1575            }
1576
1577            $dayoffset = 0;
1578            if(is_int($param2)) {
1579                $dayoffset = (int) $param2;
1580            }
1581
1582            $user = $param1;
1583            $orderby = $param3;
1584            if($param4 == 'desc')
1585                $orderdir = 'DESC';
1586            else
1587                $orderdir = 'ASC';
1588
1589            $groups = array();
1590            $tmp = $user->getGroups();
1591            foreach($tmp as $group)
1592                $groups[] = $group->getID();
1593
1594            $selectStr .= ", `tblDocumentRevisionLog`.`date` as `duedate` ";
1595            $queryStr .=
1596                "LEFT JOIN `tblDocumentRevisors` on `ttcontentid`.`document`=`tblDocumentRevisors`.`documentID` AND `ttcontentid`.`maxVersion`=`tblDocumentRevisors`.`version` ".
1597                "LEFT JOIN `ttrevisionid` ON `ttrevisionid`.`revisionID` = `tblDocumentRevisors`.`revisionID` ".
1598                "LEFT JOIN `tblDocumentRevisionLog` ON `tblDocumentRevisionLog`.`revisionLogID`=`ttrevisionid`.`maxLogID` ";
1599
1600            $queryStr .= "WHERE (`tblDocumentRevisors`.`type` = 0 AND `tblDocumentRevisors`.`required` = ".$user->getID()." ";
1601            if($groups)
1602                $queryStr .= "OR `tblDocumentRevisors`.`type` = 1 AND `tblDocumentRevisors`.`required` IN (".implode(',', $groups).")";
1603            $queryStr .= ") ";
1604            $queryStr .= "AND `tblDocumentContent`.`revisiondate` IS NOT NULL AND `tblDocumentContent`.`revisiondate` <= ".$this->db->getCurrentDatetime($dayoffset)." ";
1605            $queryStr .= "AND `tblDocumentStatusLog`.`status` = ".S_RELEASED." ";
1606            $queryStr .= " AND `tblDocumentRevisionLog`.`status` = -3 ";
1607            if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1608            else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1609            else if ($orderby=='s') $queryStr .= "ORDER BY `tblDocumentStatusLog`.`status`";
1610            else $queryStr .= "ORDER BY `name`";
1611            $queryStr .= " ".$orderdir;
1612            break; // }}}
1613        case 'DueRevision': // Documents with a due revision, which is not started {{{
1614            if (!$this->db->createTemporaryTable("ttrevisionid")) {
1615                return false;
1616            }
1617
1618            $dayoffset = 0;
1619            if(is_int($param2)) {
1620                $dayoffset = (int) $param2;
1621            }
1622
1623            $user = $param1;
1624            $orderby = $param3;
1625            if($param4 == 'desc')
1626                $orderdir = 'DESC';
1627            else
1628                $orderdir = 'ASC';
1629
1630            $selectStr .= ", `tblDocumentContent`.`revisiondate` ";
1631            $queryStr .= "WHERE `tblDocumentContent`.`revisiondate` IS NOT NULL AND `tblDocumentContent`.`revisiondate` <= ".$this->db->getCurrentDatetime($dayoffset)." ";
1632            $queryStr .= "AND `tblDocumentStatusLog`.`status` = ".S_RELEASED." ";
1633            if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1634            else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1635            else if ($orderby=='s') $queryStr .= "ORDER BY `status`";
1636            else $queryStr .= "ORDER BY `name`";
1637            $queryStr .= " ".$orderdir;
1638            $queryStr .= ", `tblDocumentContent`.`revisiondate` ASC";
1639            break; // }}}
1640        case 'WorkflowByMe': // Documents I to trigger in Worklflow {{{
1641            $user = $param1;
1642            $orderby = $param3;
1643            if($param4 == 'desc')
1644                $orderdir = 'DESC';
1645            else
1646                $orderdir = 'ASC';
1647
1648            if(1) {
1649            $groups = array();
1650            if($user) {
1651                $tmp = $user->getGroups();
1652                foreach($tmp as $group)
1653                    $groups[] = $group->getID();
1654            }
1655            $selectStr = 'distinct '.$selectStr;
1656            $queryStr .=
1657                "LEFT JOIN `tblWorkflowDocumentContent` ON `ttcontentid`.`document`=`tblWorkflowDocumentContent`.`document` AND `ttcontentid`.`maxVersion`=`tblWorkflowDocumentContent`.`version` ".
1658                "LEFT JOIN `tblWorkflowTransitions` ON `tblWorkflowDocumentContent`.`workflow`=`tblWorkflowTransitions`.`workflow` AND `tblWorkflowDocumentContent`.`state`=`tblWorkflowTransitions`.`state` ".
1659                "LEFT JOIN `tblWorkflowTransitionUsers` ON `tblWorkflowTransitionUsers`.`transition` = `tblWorkflowTransitions`.`id` ".
1660                "LEFT JOIN `tblWorkflowTransitionGroups` ON `tblWorkflowTransitionGroups`.`transition` = `tblWorkflowTransitions`.`id` ";
1661
1662            if($user) {
1663                $queryStr .= "WHERE (`tblWorkflowTransitionUsers`.`userid` = ".$user->getID()." ";
1664                if($groups)
1665                    $queryStr .= "OR `tblWorkflowTransitionGroups`.`groupid` IN (".implode(',', $groups).")";
1666                $queryStr .= ") ";
1667            }
1668            $queryStr .= "AND `tblDocumentStatusLog`.`status` = ".S_IN_WORKFLOW." ";
1669//            echo 'SELECT '.$selectStr." ".$queryStr;
1670            if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1671            else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1672            else $queryStr .= "ORDER BY `name`";
1673            } else {
1674            $queryStr .= "WHERE 1=1 ";
1675            // Get document list for the current user.
1676            $workflowStatus = $user->getWorkflowStatus();
1677
1678            // Create a comma separated list of all the documentIDs whose information is
1679            // required.
1680            $dList = array();
1681            foreach ($workflowStatus["u"] as $st) {
1682                if (!in_array($st["document"], $dList)) {
1683                    $dList[] = $st["document"];
1684                }
1685            }
1686            foreach ($workflowStatus["g"] as $st) {
1687                if (!in_array($st["document"], $dList)) {
1688                    $dList[] = $st["document"];
1689                }
1690            }
1691            $docCSV = "";
1692            foreach ($dList as $d) {
1693                $docCSV .= (strlen($docCSV)==0 ? "" : ", ")."'".$d."'";
1694            }
1695
1696            if (strlen($docCSV)>0) {
1697                $queryStr .=
1698                            //"AND `tblDocumentStatusLog`.`status` IN (".S_IN_WORKFLOW.", ".S_EXPIRED.") ".
1699                            "AND `tblDocuments`.`id` IN (" . $docCSV . ") ".
1700                            "ORDER BY `statusDate` DESC";
1701            } else {
1702                $queryStr = '';
1703            }
1704            }
1705            break; // }}}
1706        case 'AppRevOwner': // Documents waiting for review/approval/revision I'm owning {{{
1707            $queryStr .= "WHERE 1=1 ";
1708
1709            $user = $param1;
1710            $orderby = $param3;
1711            if($param4 == 'desc')
1712                $orderdir = 'DESC';
1713            else
1714                $orderdir = 'ASC';
1715            /** @noinspection PhpUndefinedConstantInspection */
1716            $queryStr .=    "AND `tblDocuments`.`owner` = '".$user->getID()."' ".
1717                "AND `tblDocumentStatusLog`.`status` IN (".S_DRAFT_REV.", ".S_DRAFT_APP.", ".S_IN_REVISION.") ";
1718            if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1719            else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1720            else if ($orderby=='s') $queryStr .= "ORDER BY `status`";
1721            else $queryStr .= "ORDER BY `name`";
1722            $queryStr .= " ".$orderdir;
1723//            $queryStr .= "AND `tblDocuments`.`owner` = '".$user->getID()."' ".
1724//                "AND `tblDocumentStatusLog`.`status` IN (".S_DRAFT_REV.", ".S_DRAFT_APP.") ".
1725//                "ORDER BY `statusDate` DESC";
1726            break; // }}}
1727        case 'ReceiveOwner': // Documents having a reception I'm owning {{{
1728            $queryStr .= "WHERE 1=1 ";
1729
1730            $user = $param1;
1731            $orderby = $param3;
1732            if($param4 == 'desc')
1733                $orderdir = 'DESC';
1734            else
1735                $orderdir = 'ASC';
1736
1737            //            $qs = 'SELECT DISTINCT `documentID` FROM `tblDocumentRecipients` LEFT JOIN `ttcontentid` ON `ttcontentid`.`maxVersion` = `tblDocumentRecipients`.`version` AND `ttcontentid`.`document` = `tblDocumentRecipients`.`documentID`';
1738            // sql statement without older versions of a document
1739            $qs = 'SELECT DISTINCT `document` as `documentID` FROM `ttcontentid` a LEFT JOIN `tblDocumentRecipients` b on a.`document`=b.`documentID` AND a.`maxVersion`=b.`version` WHERE b.`receiptID` IS NOT NULL';
1740            $ra = $this->db->getResultArray($qs);
1741            if (is_bool($ra) && !$ra) {
1742                return false;
1743            }
1744            $docs = array();
1745            foreach($ra as $d) {
1746                $docs[] = $d['documentID'];
1747            }
1748
1749            if ($docs) {
1750                $queryStr .= "AND `tblDocuments`.`id` IN (" . implode(',', $docs) . ") ";
1751                $queryStr .=    "AND `tblDocuments`.`owner` = '".$user->getID()."'";
1752                $queryStr .= "AND `tblDocumentStatusLog`.`status` IN (".S_RELEASED.") ";
1753                if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1754                else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1755                else if ($orderby=='s') $queryStr .= "ORDER BY `status`";
1756                else $queryStr .= "ORDER BY `name`";
1757                $queryStr .= " ".$orderdir;
1758            } else {
1759                $queryStr = '';
1760            }
1761            break; // }}}
1762        case 'NoReceiveOwner': // Documents *not* having a reception I'm owning {{{
1763            $queryStr .= "WHERE 1=1 ";
1764
1765            $user = $param1;
1766            $orderby = $param3;
1767            if($param4 == 'desc')
1768                $orderdir = 'DESC';
1769            else
1770                $orderdir = 'ASC';
1771
1772            //            $qs = 'SELECT DISTINCT `documentID` FROM `tblDocumentRecipients` LEFT JOIN `ttcontentid` ON `ttcontentid`.`maxVersion` = `tblDocumentRecipients`.`version` AND `ttcontentid`.`document` = `tblDocumentRecipients`.`documentID`';
1773            // sql statement without older versions of a document
1774            $qs = 'SELECT DISTINCT `document` as `documentID` FROM `ttcontentid` a LEFT JOIN `tblDocumentRecipients` b on a.`document`=b.`documentID` AND a.`maxVersion`=b.`version` WHERE b.`receiptID` IS NULL';
1775            $ra = $this->db->getResultArray($qs);
1776            if (is_bool($ra) && !$ra) {
1777                return false;
1778            }
1779            $docs = array();
1780            foreach($ra as $d) {
1781                $docs[] = $d['documentID'];
1782            }
1783
1784            if ($docs) {
1785                $queryStr .= "AND `tblDocuments`.`id` IN (" . implode(',', $docs) . ") ";
1786                $queryStr .=    "AND `tblDocuments`.`owner` = '".$user->getID()."' ".
1787                    "AND `tblDocumentStatusLog`.`status` IN (".S_RELEASED.") ";
1788                if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1789                else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1790                else if ($orderby=='s') $queryStr .= "ORDER BY `status`";
1791                else $queryStr .= "ORDER BY `name`";
1792                $queryStr .= " ".$orderdir;
1793            } else {
1794                $queryStr = '';
1795            }
1796            break; // }}}
1797        case 'RejectOwner': // Documents that has been rejected and I'm owning {{{
1798            $queryStr .= "WHERE 1=1 ";
1799
1800            $user = $param1;
1801            $orderby = $param3;
1802            if($param4 == 'desc')
1803                $orderdir = 'DESC';
1804            else
1805                $orderdir = 'ASC';
1806            $queryStr .= "AND `tblDocuments`.`owner` = '".$user->getID()."' ";
1807            $queryStr .= "AND `tblDocumentStatusLog`.`status` IN (".S_REJECTED.") ";
1808            //$queryStr .= "ORDER BY `statusDate` DESC";
1809            if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1810            else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1811            else if ($orderby=='s') $queryStr .= "ORDER BY `status`";
1812            else $queryStr .= "ORDER BY `name`";
1813            $queryStr .= " ".$orderdir;
1814            break; // }}}
1815        case 'LockedByMe': // Documents locked by me {{{
1816            $queryStr .= "WHERE 1=1 ";
1817
1818            $user = $param1;
1819            $orderby = $param3;
1820            if($param4 == 'desc')
1821                $orderdir = 'DESC';
1822            else
1823                $orderdir = 'ASC';
1824
1825            $qs = 'SELECT `document` FROM `tblDocumentLocks` WHERE `userID`='.$user->getID();
1826            $ra = $this->db->getResultArray($qs);
1827            if (is_bool($ra) && !$ra) {
1828                return false;
1829            }
1830            $docs = array();
1831            foreach($ra as $d) {
1832                $docs[] = $d['document'];
1833            }
1834
1835            if ($docs) {
1836                $queryStr .= "AND `tblDocuments`.`id` IN (" . implode(',', $docs) . ") ";
1837                if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1838                else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1839                else if ($orderby=='s') $queryStr .= "ORDER BY `status`";
1840                else $queryStr .= "ORDER BY `name`";
1841                $queryStr .= " ".$orderdir;
1842            } else {
1843                $queryStr = '';
1844            }
1845            break; // }}}
1846        case 'ExpiredOwner': // Documents expired and owned by me {{{
1847            if(is_int($param2)) {
1848                $ts = mktime(0, 0, 0) + $param2 * 86400;
1849            } elseif(is_string($param2)) {
1850                $tmp = explode('-', $param2, 3);
1851                if(count($tmp) != 3)
1852                    return false;
1853                if(!self::checkDate($param2, 'Y-m-d'))
1854                    return false;
1855                $ts = mktime(0, 0, 0, (int) $tmp[1], (int) $tmp[2], (int) $tmp[0]);
1856            } else
1857                $ts = mktime(0, 0, 0)-365*86400; /* Start of today - 1 year */
1858
1859            $tsnow = mktime(0, 0, 0); /* Start of today */
1860            if($ts < $tsnow) { /* Check for docs expired in the past */
1861                $startts = $ts;
1862                $endts = $tsnow+86400; /* Use end of day */
1863            } else { /* Check for docs which will expire in the future */
1864                $startts = $tsnow;
1865                $endts = $ts+86400; /* Use end of day */
1866            }
1867
1868            $queryStr .= 
1869                "WHERE `tblDocuments`.`expires` >= ".$startts." AND `tblDocuments`.`expires` <= ".$endts." ";
1870
1871            $user = $param1;
1872            $orderby = $param3;
1873            if($param4 == 'desc')
1874                $orderdir = 'DESC';
1875            else
1876                $orderdir = 'ASC';
1877            $queryStr .=    "AND `tblDocuments`.`owner` = '".$user->getID()."' ";
1878            if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1879            else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1880            else if ($orderby=='s') $queryStr .= "ORDER BY `status`";
1881            else $queryStr .= "ORDER BY `name`";
1882            $queryStr .= " ".$orderdir;
1883            break; // }}}
1884        case 'ObsoleteOwner': // Documents that are obsolete and I'm owning {{{
1885            $queryStr .= "WHERE 1=1 ";
1886
1887            $user = $param1;
1888            $orderby = $param3;
1889            if($param4 == 'desc')
1890                $orderdir = 'DESC';
1891            else
1892                $orderdir = 'ASC';
1893            $queryStr .= "AND `tblDocuments`.`owner` = '".$user->getID()."' ".
1894                "AND `tblDocumentStatusLog`.`status` IN (".S_OBSOLETE.") ";
1895            //$queryStr .= "ORDER BY `statusDate` DESC";
1896            if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1897            else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1898            else if ($orderby=='s') $queryStr .= "ORDER BY `status`";
1899            else $queryStr .= "ORDER BY `name`";
1900            $queryStr .= " ".$orderdir;
1901            break; // }}}
1902        case 'NeedsCorrectionOwner': // Documents that needs correction and I'm owning {{{
1903            $queryStr .= "WHERE 1=1 ";
1904
1905            $user = $param1;
1906            $orderby = $param3;
1907            if($param4 == 'desc')
1908                $orderdir = 'DESC';
1909            else
1910                $orderdir = 'ASC';
1911            $queryStr .= "AND `tblDocuments`.`owner` = '".$user->getID()."' ".
1912                "AND `tblDocumentStatusLog`.`status` IN (".S_NEEDS_CORRECTION.") ";
1913            //$queryStr .= "ORDER BY `statusDate` DESC";
1914            if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1915            else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1916            else if ($orderby=='s') $queryStr .= "ORDER BY `status`";
1917            else $queryStr .= "ORDER BY `name`";
1918            $queryStr .= " ".$orderdir;
1919            break; // }}}
1920        case 'DraftOwner': // Documents in draft status and I'm owning {{{
1921            $queryStr .= "WHERE 1=1 ";
1922
1923            $user = $param1;
1924            $orderby = $param3;
1925            if($param4 == 'desc')
1926                $orderdir = 'DESC';
1927            else
1928                $orderdir = 'ASC';
1929            $queryStr .= "AND `tblDocuments`.`owner` = '".$user->getID()."' ".
1930                "AND `tblDocumentStatusLog`.`status` IN (".S_DRAFT.") ";
1931            //$queryStr .= "ORDER BY `statusDate` DESC";
1932            if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1933            else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1934            else if ($orderby=='s') $queryStr .= "ORDER BY `status`";
1935            else $queryStr .= "ORDER BY `name`";
1936            $queryStr .= " ".$orderdir;
1937            break; // }}}
1938        case 'WorkflowOwner': // Documents waiting for workflow trigger I'm owning {{{
1939            $queryStr .= "WHERE 1=1 ";
1940
1941            $user = $param1;
1942            $queryStr .= "AND `tblDocuments`.`owner` = '".$user->getID()."' ".
1943                "AND `tblDocumentStatusLog`.`status` IN (".S_IN_WORKFLOW.") ".
1944                "ORDER BY `statusDate` DESC";
1945            break; // }}}
1946        case 'MyDocs': // Documents owned by me {{{
1947            $queryStr .= "WHERE 1=1 ";
1948
1949            $user = $param1;
1950            $orderby = $param3;
1951            if($param4 == 'desc')
1952                $orderdir = 'DESC';
1953            else
1954                $orderdir = 'ASC';
1955            $queryStr .=    "AND `tblDocuments`.`owner` = '".$user->getID()."' ";
1956            if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1957            else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1958            else if ($orderby=='s') $queryStr .= "ORDER BY `status`";
1959            else $queryStr .= "ORDER BY `name`";
1960            $queryStr .= " ".$orderdir;
1961            break; // }}}
1962        case 'CheckedOutByMe': // Documents I have checked out {{{
1963            $queryStr .= "WHERE 1=1 ";
1964
1965            $user = $param1;
1966            $orderby = $param3;
1967            if($param4 == 'desc')
1968                $orderdir = 'DESC';
1969            else
1970                $orderdir = 'ASC';
1971
1972            $qs = 'SELECT `document` FROM `tblDocumentCheckOuts` WHERE `userID`='.$user->getID();
1973            $ra = $this->db->getResultArray($qs);
1974            if (is_bool($ra) && !$ra) {
1975                return false;
1976            }
1977            $docs = array();
1978            foreach($ra as $d) {
1979                $docs[] = $d['document'];
1980            }
1981
1982            if ($docs) {
1983                $queryStr .= "AND `tblDocuments`.`id` IN (" . implode(',', $docs) . ") ";
1984                if ($orderby=='e') $queryStr .= "ORDER BY `expires`";
1985                else if ($orderby=='u') $queryStr .= "ORDER BY `statusDate`";
1986                else if ($orderby=='s') $queryStr .= "ORDER BY `status`";
1987                else $queryStr .= "ORDER BY `name`";
1988                $queryStr .= " ".$orderdir;
1989            } else {
1990                $queryStr = '';
1991            }
1992            break; // }}}
1993        default: // {{{
1994            return false;
1995            break; // }}}
1996        }
1997
1998        if($queryStr) {
1999            $resArr = $this->db->getResultArray('SELECT '.$selectStr.$queryStr);
2000            if (is_bool($resArr) && !$resArr) {
2001                return false;
2002            }
2003            /*
2004            $documents = array();
2005            foreach($resArr as $row)
2006                $documents[] = $this->getDocument($row["id"]);
2007             */
2008        } else {
2009            return array();
2010        }
2011
2012        return $resArr;
2013    } /* }}} */
2014
2015    public function makeTimeStamp($hour, $min, $sec, $year, $month, $day) { /* {{{ */
2016        $thirtyone = array (1, 3, 5, 7, 8, 10, 12);
2017        $thirty = array (4, 6, 9, 11);
2018
2019        // Very basic check that the terms are valid. Does not fail for illegal
2020        // dates such as 31 Feb.
2021        if (!is_numeric($hour) || !is_numeric($min) || !is_numeric($sec) || !is_numeric($year) || !is_numeric($month) || !is_numeric($day) || $month<1 || $month>12 || $day<1 || $day>31 || $hour<0 || $hour>23 || $min<0 || $min>59 || $sec<0 || $sec>59) {
2022            return false;
2023        }
2024        $year = (int) $year;
2025        $month = (int) $month;
2026        $day = (int) $day;
2027
2028        if(in_array($month, $thirtyone)) {
2029            $max=31;
2030        } elseif(in_array($month, $thirty)) {
2031            $max=30;
2032        } else {
2033            $max=(($year % 4 == 0) && ($year % 100 != 0 || $year % 400 == 0)) ? 29 : 28;
2034        }
2035
2036        // Check again if day of month is valid in the given month
2037        if ($day>$max) {
2038            return false;
2039        }
2040
2041        return mktime($hour, $min, $sec, $month, $day, $year);
2042    } /* }}} */
2043
2044    /**
2045     * Search the database for documents
2046     *
2047     * Note: the creation date will be used to check againts the
2048     * date saved with the document
2049     * or folder. The modification date will only be used for documents. It
2050     * is checked against the creation date of the document content. This
2051     * meanÑ• that updateÑ• of a document will only result in a searchable
2052     * modification if a new version is uploaded.
2053     *
2054     * If the search is filtered by an expiration date, only documents with
2055     * an expiration date will be found. Even if just an end date is given.
2056     *
2057     * dates, integers and floats fields are treated as ranges (expecting a 'from'
2058     * and 'to' value) unless they have a value set.
2059     *
2060     * @param string $query seach query with space separated words
2061     * @param integer $limit number of items in result set
2062     * @param integer $offset index of first item in result set
2063     * @param string $logicalmode either AND or OR
2064     * @param array $searchin list of fields to search in
2065     *        1 = keywords, 2=name, 3=comment, 4=attributes, 5=id
2066     * @param SeedDMS_Core_Folder|null $startFolder search in the folder only (null for root folder)
2067     * @param SeedDMS_Core_User $owner search for documents owned by this user
2068     * @param array $status list of status
2069     * @param array $creationstartdate search for documents created after this date
2070     * @param array $creationenddate search for documents created before this date
2071     * @param array $modificationstartdate search for documents modified after this date
2072     * @param array $modificationenddate search for documents modified before this date
2073     * @param array $categories list of categories the documents must have assigned
2074     * @param array $attributes list of attributes. The key of this array is the
2075     * attribute definition id. The value of the array is the value of the
2076     * attribute. If the attribute may have multiple values it must be an array.
2077     * attributes with a range must have the elements 'from' and 'to'
2078     * @param integer $mode decide whether to search for documents/folders
2079     *        0x1 = documents only
2080     *        0x2 = folders only
2081     *        0x3 = both
2082     * @param array $expirationstartdate search for documents expiring after and on this date
2083     * @param array $expirationenddate search for documents expiring before and on this date
2084     * @return array|bool
2085     */
2086    function search($query, $limit=0, $offset=0, $logicalmode='AND', $searchin=array(), $startFolder=null, $owner=null, $status = array(), $creationstartdate=array(), $creationenddate=array(), $modificationstartdate=array(), $modificationenddate=array(), $categories=array(), $attributes=array(), $mode=0x3, $expirationstartdate=array(), $expirationenddate=array(), $reception=array()) { /* {{{ */
2087        $orderby = '';
2088        $revisionstartdate = $revisionenddate = '';
2089        $statusstartdate = array();
2090        $statusenddate = array();
2091        if(is_array($query)) {
2092            foreach(array('limit', 'offset', 'logicalmode', 'searchin', 'startFolder', 'owner', 'status', 'creationstartdate', 'creationenddate', 'modificationstartdate', 'modificationenddate', 'categories', 'attributes', 'mode', 'revisionstartdate', 'revisionenddate', 'expirationstartdate', 'expirationenddate', 'reception') as $paramname)
2093                ${$paramname} = isset($query[$paramname]) ? $query[$paramname] : ${$paramname};
2094            foreach(array('orderby', 'statusstartdate', 'statusenddate') as $paramname)
2095                ${$paramname} = isset($query[$paramname]) ? $query[$paramname] : '';
2096            $query = isset($query['query']) ? $query['query'] : '';
2097        }
2098        /* Ensure $logicalmode has a valid value */
2099        if($logicalmode != 'OR')
2100            $logicalmode = 'AND';
2101
2102        // Split the search string into constituent keywords.
2103        $tkeys=array();
2104        if (strlen($query)>0) {
2105            $tkeys = preg_split("/[\t\r\n ,]+/", $query);
2106        }
2107
2108        // if none is checkd search all
2109        if (count($searchin)==0)
2110            $searchin=array(1, 2, 3, 4, 5);
2111
2112        /*--------- Do it all over again for folders -------------*/
2113        $totalFolders = 0;
2114        if($mode & 0x2) {
2115            $searchKey = "";
2116
2117            $classname = $this->classnames['folder'];
2118            $searchFields = $classname::getSearchFields($this, $searchin);
2119
2120            if (count($searchFields)>0) {
2121                foreach ($tkeys as $key) {
2122                    $key = trim($key);
2123                    if (strlen($key)>0) {
2124                        $searchKey = (strlen($searchKey)==0 ? "" : $searchKey." ".$logicalmode." ")."(".implode(" like ".$this->db->qstr("%".$key."%")." OR ", $searchFields)." like ".$this->db->qstr("%".$key."%").")";
2125                    }
2126                }
2127            }
2128
2129            // Check to see if the search has been restricted to a particular sub-tree in
2130            // the folder hierarchy.
2131            $searchFolder = "";
2132            if ($startFolder) {
2133                $searchFolder = "`tblFolders`.`folderList` LIKE '%:".$startFolder->getID().":%'";
2134                if($this->checkWithinRootDir)
2135                    $searchFolder = '('.$searchFolder." AND `tblFolders`.`folderList` LIKE '%:".$this->rootFolderID.":%')";
2136            } elseif($this->checkWithinRootDir) {
2137                $searchFolder = "`tblFolders`.`folderList` LIKE '%:".$this->rootFolderID.":%'";
2138            }
2139
2140            // Check to see if the search has been restricted to a particular
2141            // document owner.
2142            $searchOwner = "";
2143            if ($owner) {
2144                if(is_array($owner)) {
2145                    $ownerids = array();
2146                    foreach($owner as $o)
2147                        $ownerids[] = $o->getID();
2148                    if($ownerids)
2149                        $searchOwner = "`tblFolders`.`owner` IN (".implode(',', $ownerids).")";
2150                } else {
2151                    $searchOwner = "`tblFolders`.`owner` = '".$owner->getId()."'";
2152                }
2153            }
2154
2155            // Check to see if the search has been restricted to a particular
2156            // attribute.
2157            $searchAttributes = array();
2158            if ($attributes) {
2159                foreach($attributes as $attrdefid=>$attribute) {
2160                    if($attribute) {
2161                        $attrdef = $this->getAttributeDefinition($attrdefid);
2162                        if($attrdef->getObjType() == SeedDMS_Core_AttributeDefinition::objtype_folder || $attrdef->getObjType() == SeedDMS_Core_AttributeDefinition::objtype_all) {
2163                            if($valueset = $attrdef->getValueSet()) {
2164                                if(is_string($attribute))
2165                                    $attribute = array($attribute);
2166                                foreach($attribute as &$v)
2167                                    $v = trim($this->db->qstr($v), "'");
2168                                if($attrdef->getMultipleValues()) {
2169                                    $searchAttributes[] = "EXISTS (SELECT NULL FROM `tblFolderAttributes` WHERE `tblFolderAttributes`.`attrdef`=".$attrdefid." AND (`tblFolderAttributes`.`value` like '%".$valueset[0].implode("%' OR `tblFolderAttributes`.`value` like '%".$valueset[0], $attribute)."%') AND `tblFolderAttributes`.`folder`=`tblFolders`.`id`)";
2170                                } else {
2171                                    $searchAttributes[] = "EXISTS (SELECT NULL FROM `tblFolderAttributes` WHERE `tblFolderAttributes`.`attrdef`=".$attrdefid." AND (`tblFolderAttributes`.`value`='".(is_array($attribute) ? implode("' OR `tblFolderAttributes`.`value` = '", $attribute) : $attribute)."') AND `tblFolderAttributes`.`folder`=`tblFolders`.`id`)";
2172                                }
2173                            } else {
2174                                if(in_array($attrdef->getType(), [SeedDMS_Core_AttributeDefinition::type_date, SeedDMS_Core_AttributeDefinition::type_int, SeedDMS_Core_AttributeDefinition::type_float]) && is_array($attribute)) {
2175                                    $kkll = [];
2176                                    if(!empty($attribute['from'])) {
2177                                        if($attrdef->getType() == SeedDMS_Core_AttributeDefinition::type_int)
2178                                            $kkll[] = "CAST(`tblFolderAttributes`.`value` AS INTEGER)>=".(int) $attribute['from'];
2179                                        elseif($attrdef->getType() == SeedDMS_Core_AttributeDefinition::type_float)
2180                                            $kkll[] = "CAST(`tblFolderAttributes`.`value` AS DECIMAL)>=".(float) $attribute['from'];
2181                                        else
2182                                            $kkll[] = "`tblFolderAttributes`.`value`>=".$this->db->qstr($attribute['from']);
2183                                    }
2184                                    if(!empty($attribute['to'])) {
2185                                        if($attrdef->getType() == SeedDMS_Core_AttributeDefinition::type_int)
2186                                            $kkll[] = "CAST(`tblFolderAttributes`.`value` AS INTEGER)<=".(int) $attribute['to'];
2187                                        elseif($attrdef->getType() == SeedDMS_Core_AttributeDefinition::type_float)
2188                                            $kkll[] = "CAST(`tblFolderAttributes`.`value` AS DECIMAL)<=".(float) $attribute['to'];
2189                                        else
2190                                            $kkll[] = "`tblFolderAttributes`.`value`<=".$this->db->qstr($attribute['to']);
2191                                    }
2192                                    if($kkll)
2193                                        $searchAttributes[] = "EXISTS (SELECT NULL FROM `tblFolderAttributes` WHERE `tblFolderAttributes`.`attrdef`=".$attrdefid." AND ".implode(' AND ', $kkll)." AND `tblFolderAttributes`.`folder`=`tblFolders`.`id`)";
2194                                } elseif(is_string($attribute)) {
2195                                    $searchAttributes[] = "EXISTS (SELECT NULL FROM `tblFolderAttributes` WHERE `tblFolderAttributes`.`attrdef`=".$attrdefid." AND `tblFolderAttributes`.`value` like ".$this->db->qstr("%".$attribute."%")." AND `tblFolderAttributes`.`folder`=`tblFolders`.`id`)";
2196                                }
2197                            }
2198                        }
2199                    }
2200                }
2201            }
2202
2203            // Is the search restricted to documents created between two specific dates?
2204            $searchCreateDate = "";
2205            if ($creationstartdate) {
2206                if(is_numeric($creationstartdate))
2207                    $startdate = $creationstartdate;
2208                else
2209                    $startdate = SeedDMS_Core_DMS::makeTimeStamp($creationstartdate['hour'], $creationstartdate['minute'], $creationstartdate['second'], $creationstartdate['year'], $creationstartdate["month"], $creationstartdate["day"]);
2210                if ($startdate) {
2211                    $searchCreateDate .= "`tblFolders`.`date` >= ".$this->db->qstr($startdate);
2212                }
2213            }
2214            if ($creationenddate) {
2215                if(is_numeric($creationenddate))
2216                    $stopdate = $creationenddate;
2217                else
2218                    $stopdate = SeedDMS_Core_DMS::makeTimeStamp($creationenddate['hour'], $creationenddate['minute'], $creationenddate['second'], $creationenddate["year"], $creationenddate["month"], $creationenddate["day"]);
2219                if ($stopdate) {
2220                    /** @noinspection PhpUndefinedVariableInspection */
2221                    if($startdate)
2222                        $searchCreateDate .= " AND ";
2223                    $searchCreateDate .= "`tblFolders`.`date` <= ".$this->db->qstr($stopdate);
2224                }
2225            }
2226
2227            $searchQuery = "FROM ".$classname::getSearchTables()." WHERE 1=1";
2228
2229            if (strlen($searchKey)>0) {
2230                $searchQuery .= " AND (".$searchKey.")";
2231            }
2232            if (strlen($searchFolder)>0) {
2233                $searchQuery .= " AND ".$searchFolder;
2234            }
2235            if (strlen($searchOwner)>0) {
2236                $searchQuery .= " AND (".$searchOwner.")";
2237            }
2238            if (strlen($searchCreateDate)>0) {
2239                $searchQuery .= " AND (".$searchCreateDate.")";
2240            }
2241            if ($searchAttributes) {
2242                $searchQuery .= " AND (".implode(" AND ", $searchAttributes).")";
2243            }
2244
2245            /* Do not search for folders if not at least a search for a key,
2246             * an owner, or creation date is requested.
2247             */
2248            if($searchKey || $searchOwner || $searchCreateDate || $searchAttributes) {
2249                // Count the number of rows that the search will produce.
2250                $resArr = $this->db->getResultArray("SELECT COUNT(*) AS num FROM (SELECT DISTINCT `tblFolders`.id ".$searchQuery.") a");
2251                if ($resArr && isset($resArr[0]) && is_numeric($resArr[0]["num"]) && $resArr[0]["num"]>0) {
2252                    $totalFolders = (integer)$resArr[0]["num"];
2253                }
2254
2255                // If there are no results from the count query, then there is no real need
2256                // to run the full query. TODO: re-structure code to by-pass additional
2257                // queries when no initial results are found.
2258
2259                // Only search if the offset is not beyond the number of folders
2260                if($totalFolders > $offset) {
2261                    // Prepare the complete search query, including the LIMIT clause.
2262                    $searchQuery = "SELECT DISTINCT `tblFolders`.`id` ".$searchQuery." GROUP BY `tblFolders`.`id`";
2263
2264                    switch($orderby) {
2265                    case 'dd':
2266                        $searchQuery .= " ORDER BY `tblFolders`.`date` DESC";
2267                        break;
2268                    case 'da':
2269                    case 'd':
2270                        $searchQuery .= " ORDER BY `tblFolders`.`date`";
2271                        break;
2272                    case 'nd':
2273                        $searchQuery .= " ORDER BY `tblFolders`.`name` DESC";
2274                        break;
2275                    case 'na':
2276                    case 'n':
2277                        $searchQuery .= " ORDER BY `tblFolders`.`name`";
2278                        break;
2279                    case 'id':
2280                        $searchQuery .= " ORDER BY `tblFolders`.`id` DESC";
2281                        break;
2282                    case 'ia':
2283                    case 'i':
2284                        $searchQuery .= " ORDER BY `tblFolders`.`id`";
2285                        break;
2286                    default:
2287                        break;
2288                    }
2289
2290                    if($limit) {
2291                        $searchQuery .= " LIMIT ".$limit." OFFSET ".$offset;
2292                    }
2293
2294                    // Send the complete search query to the database.
2295                    $resArr = $this->db->getResultArray($searchQuery);
2296                } else {
2297                    $resArr = array();
2298                }
2299
2300                // ------------------- Ausgabe der Ergebnisse ----------------------------
2301                $numResults = count($resArr);
2302                if ($numResults == 0) {
2303                    $folderresult = array('totalFolders'=>$totalFolders, 'folders'=>array());
2304                } else {
2305                    foreach ($resArr as $folderArr) {
2306                        $folders[] = $this->getFolder($folderArr['id']);
2307                    }
2308                    /** @noinspection PhpUndefinedVariableInspection */
2309                    $folderresult = array('totalFolders'=>$totalFolders, 'folders'=>$folders);
2310                }
2311            } else {
2312                $folderresult = array('totalFolders'=>0, 'folders'=>array());
2313            }
2314        } else {
2315            $folderresult = array('totalFolders'=>0, 'folders'=>array());
2316        }
2317
2318        /*--------- Do it all over again for documents -------------*/
2319
2320        $totalDocs = 0;
2321        if($mode & 0x1) {
2322            $searchKey = "";
2323
2324            $classname = $this->classnames['document'];
2325            $searchFields = $classname::getSearchFields($this, $searchin);
2326
2327            if (count($searchFields)>0) {
2328                foreach ($tkeys as $key) {
2329                    $key = trim($key);
2330                    if (strlen($key)>0) {
2331                        $searchKey = (strlen($searchKey)==0 ? "" : $searchKey." ".$logicalmode." ")."(".implode(" like ".$this->db->qstr("%".$key."%")." OR ", $searchFields)." like ".$this->db->qstr("%".$key."%").")";
2332                    }
2333                }
2334            }
2335
2336            // Check to see if the search has been restricted to a particular sub-tree in
2337            // the folder hierarchy.
2338            $searchFolder = "";
2339            if ($startFolder) {
2340                $searchFolder = "`tblDocuments`.`folderList` LIKE '%:".$startFolder->getID().":%'";
2341                if($this->checkWithinRootDir)
2342                    $searchFolder = '('.$searchFolder." AND `tblDocuments`.`folderList` LIKE '%:".$this->rootFolderID.":%')";
2343            } elseif($this->checkWithinRootDir) {
2344                $searchFolder = "`tblDocuments`.`folderList` LIKE '%:".$this->rootFolderID.":%'";
2345            }
2346
2347            // Check to see if the search has been restricted to a particular
2348            // document owner.
2349            $searchOwner = "";
2350            if ($owner) {
2351                if(is_array($owner)) {
2352                    $ownerids = array();
2353                    foreach($owner as $o)
2354                        $ownerids[] = $o->getID();
2355                    if($ownerids)
2356                        $searchOwner = "`tblDocuments`.`owner` IN (".implode(',', $ownerids).")";
2357                } else {
2358                    $searchOwner = "`tblDocuments`.`owner` = '".$owner->getId()."'";
2359                }
2360            }
2361
2362            // Check to see if the search has been restricted to a particular
2363            // document category.
2364            $searchCategories = "";
2365            if ($categories) {
2366                $catids = array();
2367                foreach($categories as $category)
2368                    $catids[] = $category->getId();
2369                $searchCategories = "`tblDocumentCategory`.`categoryID` in (".implode(',', $catids).")";
2370            }
2371
2372            // Check to see if the search has been restricted to a particular
2373            // attribute.
2374            $searchAttributes = array();
2375            if ($attributes) {
2376                foreach($attributes as $attrdefid=>$attribute) {
2377                    if($attribute) {
2378                        $lsearchAttributes = [];
2379                        $attrdef = $this->getAttributeDefinition($attrdefid);
2380                        if($attrdef->getObjType() == SeedDMS_Core_AttributeDefinition::objtype_document || $attrdef->getObjType() == SeedDMS_Core_AttributeDefinition::objtype_all) {
2381                            if($valueset = $attrdef->getValueSet()) {
2382                                if(is_string($attribute))
2383                                    $attribute = array($attribute);
2384                                foreach($attribute as &$v)
2385                                    $v = trim($this->db->qstr($v), "'");
2386                                if($attrdef->getMultipleValues()) {
2387                                    $lsearchAttributes[] = "EXISTS (SELECT NULL FROM `tblDocumentAttributes` WHERE `tblDocumentAttributes`.`attrdef`=".$attrdefid." AND (`tblDocumentAttributes`.`value` like '%".$valueset[0].implode("%' OR `tblDocumentAttributes`.`value` like '%".$valueset[0], $attribute)."%') AND `tblDocumentAttributes`.`document` = `tblDocuments`.`id`)";
2388                                } else {
2389                                    $lsearchAttributes[] = "EXISTS (SELECT NULL FROM `tblDocumentAttributes` WHERE `tblDocumentAttributes`.`attrdef`=".$attrdefid." AND (`tblDocumentAttributes`.`value`='".(is_array($attribute) ? implode("' OR `tblDocumentAttributes`.`value` = '", $attribute) : $attribute)."') AND `tblDocumentAttributes`.`document` = `tblDocuments`.`id`)";
2390                                }
2391                            } else {
2392                                if(in_array($attrdef->getType(), [SeedDMS_Core_AttributeDefinition::type_date, SeedDMS_Core_AttributeDefinition::type_int, SeedDMS_Core_AttributeDefinition::type_float]) && is_array($attribute)) {
2393                                    $kkll = [];
2394                                    if(!empty($attribute['from'])) {
2395                                        if($attrdef->getType() == SeedDMS_Core_AttributeDefinition::type_int)
2396                                            $kkll[] = "CAST(`tblDocumentAttributes`.`value` AS INTEGER)>=".(int) $attribute['from'];
2397                                        elseif($attrdef->getType() == SeedDMS_Core_AttributeDefinition::type_float)
2398                                            $kkll[] = "CAST(`tblDocumentAttributes`.`value` AS DECIMAL)>=".(float) $attribute['from'];
2399                                        else
2400                                            $kkll[] = "`tblDocumentAttributes`.`value`>=".$this->db->qstr($attribute['from']);
2401                                    }
2402                                    if(!empty($attribute['to'])) {
2403                                        if($attrdef->getType() == SeedDMS_Core_AttributeDefinition::type_int)
2404                                            $kkll[] = "CAST(`tblDocumentAttributes`.`value` AS INTEGER)<=".(int) $attribute['to'];
2405                                        elseif($attrdef->getType() == SeedDMS_Core_AttributeDefinition::type_float)
2406                                            $kkll[] = "CAST(`tblDocumentAttributes`.`value` AS DECIMAL)<=".(float) $attribute['to'];
2407                                        else
2408                                            $kkll[] = "`tblDocumentAttributes`.`value`<=".$this->db->qstr($attribute['to']);
2409                                    }
2410                                    if($kkll)
2411                                        $lsearchAttributes[] = "EXISTS (SELECT NULL FROM `tblDocumentAttributes` WHERE `tblDocumentAttributes`.`attrdef`=".$attrdefid." AND ".implode(' AND ', $kkll)." AND `tblDocumentAttributes`.`document`=`tblDocuments`.`id`)";
2412                                } else {
2413                                    $lsearchAttributes[] = "EXISTS (SELECT NULL FROM `tblDocumentAttributes` WHERE `tblDocumentAttributes`.`attrdef`=".$attrdefid." AND `tblDocumentAttributes`.`value` like ".$this->db->qstr("%".$attribute."%")." AND `tblDocumentAttributes`.`document` = `tblDocuments`.`id`)";
2414                                }
2415                            }
2416                        }
2417                        if($attrdef->getObjType() == SeedDMS_Core_AttributeDefinition::objtype_documentcontent || $attrdef->getObjType() == SeedDMS_Core_AttributeDefinition::objtype_all) {
2418                            if($valueset = $attrdef->getValueSet()) {
2419                                if(is_string($attribute))
2420                                    $attribute = array($attribute);
2421                                foreach($attribute as &$v)
2422                                    $v = trim($this->db->qstr($v), "'");
2423                                if($attrdef->getMultipleValues()) {
2424                                    $lsearchAttributes[] = "EXISTS (SELECT NULL FROM `tblDocumentContentAttributes` WHERE `tblDocumentContentAttributes`.`attrdef`=".$attrdefid." AND (`tblDocumentContentAttributes`.`value` like '%".$valueset[0].implode("%' OR `tblDocumentContentAttributes`.`value` like '%".$valueset[0], $attribute)."%') AND `tblDocumentContentAttributes`.`content` = `tblDocumentContent`.`id`)";
2425                                } else {
2426                                    $lsearchAttributes[] = "EXISTS (SELECT NULL FROM `tblDocumentContentAttributes` WHERE `tblDocumentContentAttributes`.`attrdef`=".$attrdefid." AND (`tblDocumentContentAttributes`.`value`='".(is_array($attribute) ? implode("' OR `tblDocumentContentAttributes`.`value` = '", $attribute) : $attribute)."') AND `tblDocumentContentAttributes`.content = `tblDocumentContent`.id)";
2427                                }
2428                            } else {
2429                                if(in_array($attrdef->getType(), [SeedDMS_Core_AttributeDefinition::type_date, SeedDMS_Core_AttributeDefinition::type_int, SeedDMS_Core_AttributeDefinition::type_float]) && is_array($attribute)) {
2430                                    $kkll = [];
2431                                    if(!empty($attribute['from'])) {
2432                                        if($attrdef->getType() == SeedDMS_Core_AttributeDefinition::type_int)
2433                                            $kkll[] = "CAST(`tblDocumentContentAttributes`.`value` AS INTEGER)>=".(int) $attribute['from'];
2434                                        elseif($attrdef->getType() == SeedDMS_Core_AttributeDefinition::type_float)
2435                                            $kkll[] = "CAST(`tblDocumentContentAttributes`.`value` AS DECIMAL)>=".(float) $attribute['from'];
2436                                        else
2437                                            $kkll[] = "`tblDocumentContentAttributes`.`value`>=".$this->db->qstr($attribute['from']);
2438                                    }
2439                                    if(!empty($attribute['to'])) {
2440                                        if($attrdef->getType() == SeedDMS_Core_AttributeDefinition::type_int)
2441                                            $kkll[] = "CAST(`tblDocumentContentAttributes`.`value` AS INTEGER)<=".(int) $attribute['to'];
2442                                        elseif($attrdef->getType() == SeedDMS_Core_AttributeDefinition::type_float)
2443                                            $kkll[] = "CAST(`tblDocumentContentAttributes`.`value` AS DECIMAL)<=".(float) $attribute['to'];
2444                                        else
2445                                            $kkll[] = "`tblDocumentContentAttributes`.`value`<=".$this->db->qstr($attribute['to']);
2446                                    }
2447                                    if($kkll)
2448                                        $lsearchAttributes[] = "EXISTS (SELECT NULL FROM `tblDocumentContentAttributes` WHERE `tblDocumentContentAttributes`.`attrdef`=".$attrdefid." AND ".implode(' AND ', $kkll)." AND `tblDocumentContentAttributes`.`content`=`tblDocumentContent`.`id`)";
2449                                } else {
2450                                    $lsearchAttributes[] = "EXISTS (SELECT NULL FROM `tblDocumentContentAttributes` WHERE `tblDocumentContentAttributes`.`attrdef`=".$attrdefid." AND `tblDocumentContentAttributes`.`value` like ".$this->db->qstr("%".$attribute."%")." AND `tblDocumentContentAttributes`.content = `tblDocumentContent`.id)";
2451                                }
2452                            }
2453                        }
2454                        if($lsearchAttributes)
2455                            $searchAttributes[] = "(".implode(" OR ", $lsearchAttributes).")";
2456                    }
2457                }
2458            }
2459
2460            // Is the search restricted to documents created between two specific dates?
2461            $searchCreateDate = "";
2462            if ($creationstartdate) {
2463                if(is_numeric($creationstartdate))
2464                    $startdate = $creationstartdate;
2465                else
2466                    $startdate = SeedDMS_Core_DMS::makeTimeStamp($creationstartdate['hour'], $creationstartdate['minute'], $creationstartdate['second'], $creationstartdate['year'], $creationstartdate["month"], $creationstartdate["day"]);
2467                if ($startdate) {
2468                    $searchCreateDate .= "`tblDocuments`.`date` >= ".(int) $startdate;
2469                }
2470            }
2471            if ($creationenddate) {
2472                if(is_numeric($creationenddate))
2473                    $stopdate = $creationenddate;
2474                else
2475                    $stopdate = SeedDMS_Core_DMS::makeTimeStamp($creationenddate['hour'], $creationenddate['minute'], $creationenddate['second'], $creationenddate["year"], $creationenddate["month"], $creationenddate["day"]);
2476                if ($stopdate) {
2477                    if($searchCreateDate)
2478                        $searchCreateDate .= " AND ";
2479                    $searchCreateDate .= "`tblDocuments`.`date` <= ".(int) $stopdate;
2480                }
2481            }
2482
2483            if ($modificationstartdate) {
2484                if(is_numeric($modificationstartdate))
2485                    $startdate = $modificationstartdate;
2486                else
2487                    $startdate = SeedDMS_Core_DMS::makeTimeStamp($modificationstartdate['hour'], $modificationstartdate['minute'], $modificationstartdate['second'], $modificationstartdate['year'], $modificationstartdate["month"], $modificationstartdate["day"]);
2488                if ($startdate) {
2489                    if($searchCreateDate)
2490                        $searchCreateDate .= " AND ";
2491                    $searchCreateDate .= "`tblDocumentContent`.`date` >= ".(int) $startdate;
2492                }
2493            }
2494            if ($modificationenddate) {
2495                if(is_numeric($modificationenddate))
2496                    $stopdate = $modificationenddate;
2497                else
2498                    $stopdate = SeedDMS_Core_DMS::makeTimeStamp($modificationenddate['hour'], $modificationenddate['minute'], $modificationenddate['second'], $modificationenddate["year"], $modificationenddate["month"], $modificationenddate["day"]);
2499                if ($stopdate) {
2500                    if($searchCreateDate)
2501                        $searchCreateDate .= " AND ";
2502                    $searchCreateDate .= "`tblDocumentContent`.`date` <= ".(int) $stopdate;
2503                }
2504            }
2505            $searchRevisionDate = "";
2506            if ($revisionstartdate) {
2507                $startdate = sprintf('%04d-%02d-%02d', $revisionstartdate['year'], $revisionstartdate["month"], $revisionstartdate["day"]);
2508                if ($startdate) {
2509                    if($searchRevisionDate)
2510                        $searchRevisionDate .= " AND ";
2511                    $searchRevisionDate .= "`tblDocumentContent`.`revisiondate` >= '".$startdate."'";
2512                }
2513            }
2514            if ($revisionenddate) {
2515                $stopdate = sprintf('%04d-%02d-%02d', $revisionenddate["year"], $revisionenddate["month"], $revisionenddate["day"]);
2516                if ($stopdate) {
2517                    if($searchRevisionDate)
2518                        $searchRevisionDate .= " AND ";
2519                    $searchRevisionDate .= "`tblDocumentContent`.`revisiondate` <= '".$stopdate."'";
2520                }
2521            }
2522            $searchExpirationDate = '';
2523            if ($expirationstartdate) {
2524                $startdate = SeedDMS_Core_DMS::makeTimeStamp($expirationstartdate['hour'], $expirationstartdate['minute'], $expirationstartdate['second'], $expirationstartdate['year'], $expirationstartdate["month"], $expirationstartdate["day"]);
2525                if ($startdate) {
2526                    $searchExpirationDate .= "`tblDocuments`.`expires` >= ".(int) $startdate;
2527                }
2528            }
2529            if ($expirationenddate) {
2530                $stopdate = SeedDMS_Core_DMS::makeTimeStamp($expirationenddate['hour'], $expirationenddate['minute'], $expirationenddate['second'], $expirationenddate["year"], $expirationenddate["month"], $expirationenddate["day"]);
2531                if ($stopdate) {
2532                    if($searchExpirationDate)
2533                        $searchExpirationDate .= " AND ";
2534                    else // do not find documents without an expiration date
2535                        $searchExpirationDate .= "`tblDocuments`.`expires` != 0 AND ";
2536                    $searchExpirationDate .= "`tblDocuments`.`expires` <= ".(int) $stopdate;
2537                }
2538            }
2539            $searchStatusDate = '';
2540            if ($statusstartdate) {
2541                $startdate = $statusstartdate['year'].'-'.$statusstartdate["month"].'-'.$statusstartdate["day"].' '.$statusstartdate['hour'].':'.$statusstartdate['minute'].':'.$statusstartdate['second'];
2542                if ($startdate) {
2543                    if($searchStatusDate)
2544                        $searchStatusDate .= " AND ";
2545                    $searchStatusDate .= "`tblDocumentStatusLog`.`date` >= ".$this->db->qstr($startdate);
2546                }
2547            }
2548            if ($statusenddate) {
2549                $stopdate = $statusenddate['year'].'-'.$statusenddate["month"].'-'.$statusenddate["day"].' '.$statusenddate['hour'].':'.$statusenddate['minute'].':'.$statusenddate['second'];
2550                if ($stopdate) {
2551                    if($searchStatusDate)
2552                        $searchStatusDate .= " AND ";
2553                    $searchStatusDate .= "`tblDocumentStatusLog`.`date` <= ".$this->db->qstr($stopdate);
2554                }
2555            }
2556
2557            // ---------------------- Suche starten ----------------------------------
2558
2559            //
2560            // Construct the SQL query that will be used to search the database.
2561            //
2562
2563            if (!$this->db->createTemporaryTable("ttcontentid") || !$this->db->createTemporaryTable("ttstatid")) {
2564                return false;
2565            }
2566            if($reception) {
2567                if (!$this->db->createTemporaryTable("ttreceiptid")) {
2568                    return false;
2569                }
2570            }
2571
2572            $searchQuery = "FROM `tblDocuments` ".
2573                "LEFT JOIN `tblDocumentContent` ON `tblDocuments`.`id` = `tblDocumentContent`.`document` ".
2574                "LEFT JOIN `tblDocumentAttributes` ON `tblDocuments`.`id` = `tblDocumentAttributes`.`document` ".
2575                "LEFT JOIN `tblDocumentContentAttributes` ON `tblDocumentContent`.`id` = `tblDocumentContentAttributes`.`content` ".
2576                "LEFT JOIN `tblDocumentStatus` ON `tblDocumentStatus`.`documentID` = `tblDocumentContent`.`document` ".
2577                "LEFT JOIN `ttstatid` ON `ttstatid`.`statusID` = `tblDocumentStatus`.`statusID` ".
2578                "LEFT JOIN `tblDocumentStatusLog` ON `tblDocumentStatusLog`.`statusLogID` = `ttstatid`.`maxLogID` ".
2579                "LEFT JOIN `ttcontentid` ON `ttcontentid`.`maxVersion` = `tblDocumentStatus`.`version` AND `ttcontentid`.`document` = `tblDocumentStatus`.`documentID` ".
2580                "LEFT JOIN `tblDocumentLocks` ON `tblDocuments`.`id`=`tblDocumentLocks`.`document` ".
2581                "LEFT JOIN `tblDocumentCategory` ON `tblDocuments`.`id`=`tblDocumentCategory`.`documentID` ".
2582//                "LEFT JOIN `tblDocumentRecipients` ON `tblDocuments`.`id`=`tblDocumentRecipients`.`documentID` ".
2583//                "LEFT JOIN `tblDocumentReceiptLog` ON `tblDocumentRecipients`.`receiptID`=`tblDocumentReceiptLog`.`receiptID` ".
2584//                "LEFT JOIN `ttreceiptid` ON `ttreceiptid`.`maxLogID` = `tblDocumentReceiptLog`.`receiptLogID` ".
2585                "WHERE ".
2586//                "`ttstatid`.`maxLogID`=`tblDocumentStatusLog`.`statusLogID` AND ".
2587//                "`ttreceiptid`.`maxLogID`=`tblDocumentReceiptLog`.`receiptLogID` AND ".
2588                "`ttcontentid`.`maxVersion` = `tblDocumentContent`.`version`";
2589
2590            if (strlen($searchKey)>0) {
2591                $searchQuery .= " AND (".$searchKey.")";
2592            }
2593            if (strlen($searchFolder)>0) {
2594                $searchQuery .= " AND ".$searchFolder;
2595            }
2596            if (strlen($searchOwner)>0) {
2597                $searchQuery .= " AND (".$searchOwner.")";
2598            }
2599            if (strlen($searchCategories)>0) {
2600                $searchQuery .= " AND (".$searchCategories.")";
2601            }
2602            if (strlen($searchCreateDate)>0) {
2603                $searchQuery .= " AND (".$searchCreateDate.")";
2604            }
2605            if (strlen($searchRevisionDate)>0) {
2606                $searchQuery .= " AND (".$searchRevisionDate.")";
2607            }
2608            if (strlen($searchExpirationDate)>0) {
2609                $searchQuery .= " AND (".$searchExpirationDate.")";
2610            }
2611            if (strlen($searchStatusDate)>0) {
2612                $searchQuery .= " AND (".$searchStatusDate.")";
2613            }
2614            if ($searchAttributes) {
2615                $searchQuery .= " AND (".implode(" AND ", $searchAttributes).")";
2616            }
2617
2618            // status
2619            if ($status) {
2620                $searchQuery .= " AND `tblDocumentStatusLog`.`status` IN (".implode(',', $status).")";
2621            }
2622
2623            if($reception) {
2624                $searchReception = array();
2625                /* still waiting for users/groups to acknownledge reception */
2626                if(in_array("missingaction", $reception))
2627                    $searchReception[] = "b.`status` IN (0)";
2628                /* document has not been acknowledeged by at least one user/group */
2629                if(in_array("hasrejection", $reception))
2630                    $searchReception[] = "b.`status` IN (-1)";
2631                /* document has been acknowledeged by at least one user/group */
2632                if(in_array("hasacknowledge", $reception))
2633                    $searchReception[] = "b.`status` IN (1)";
2634                /* document has been acknowledeged by all users/groups !!! not working !!! */
2635                if(in_array("completeacknowledge", $reception))
2636                    $searchReception[] = "b.`status` NOT IN (-1, 0)";
2637                if($searchReception) {
2638                    $searchQuery .= " AND EXISTS (SELECT NULL FROM `tblDocumentRecipients` a LEFT JOIN `tblDocumentReceiptLog` b ON a.`receiptID`=b.`receiptID` LEFT JOIN `ttreceiptid` c ON c.`maxLogID` = b.`receiptLogID` WHERE ";
2639                    $searchQuery .= "c.`maxLogID`=b.`receiptLogID` AND `tblDocuments`.`id` = a.`documentID` ";
2640                    $searchQuery .= "AND (".implode(' OR ', $searchReception)."))";
2641                }
2642            }
2643
2644            if($searchKey || $searchOwner || $searchCategories || $searchCreateDate || $searchRevisionDate || $searchExpirationDate || $searchStatusDate || $searchAttributes || $status) {
2645                // Count the number of rows that the search will produce.
2646                $resArr = $this->db->getResultArray("SELECT COUNT(*) AS num FROM (SELECT DISTINCT `tblDocuments`.`id` ".$searchQuery.") a");
2647                $totalDocs = 0;
2648                if (is_numeric($resArr[0]["num"]) && $resArr[0]["num"]>0) {
2649                    $totalDocs = (integer)$resArr[0]["num"];
2650                }
2651
2652                // If there are no results from the count query, then there is no real need
2653                // to run the full query. TODO: re-structure code to by-pass additional
2654                // queries when no initial results are found.
2655
2656                // Prepare the complete search query, including the LIMIT clause.
2657                $searchQuery = "SELECT DISTINCT `tblDocuments`.*, ".
2658                    "`tblDocumentContent`.`version`, ".
2659                    "`tblDocumentStatusLog`.`status`, `tblDocumentLocks`.`userID` as `lockUser` ".$searchQuery;
2660
2661                switch($orderby) {
2662                case 'dd':
2663                    $orderbyQuery = " ORDER BY `tblDocuments`.`date` DESC";
2664                    break;
2665                case 'da':
2666                case 'd':
2667                    $orderbyQuery = " ORDER BY `tblDocuments`.`date`";
2668                    break;
2669                case 'nd':
2670                    $orderbyQuery = " ORDER BY `tblDocuments`.`name` DESC";
2671                    break;
2672                case 'na':
2673                case 'n':
2674                    $orderbyQuery = " ORDER BY `tblDocuments`.`name`";
2675                    break;
2676                case 'id':
2677                    $orderbyQuery = " ORDER BY `tblDocuments`.`id` DESC";
2678                    break;
2679                case 'ia':
2680                case 'i':
2681                    $orderbyQuery = " ORDER BY `tblDocuments`.`id`";
2682                    break;
2683                default:
2684                    $orderbyQuery = "";
2685                    break;
2686                }
2687
2688                // calculate the remaining entrÑ—es of the current page
2689                // If page is not full yet, get remaining entries
2690                if($limit) {
2691                    $remain = $limit - count($folderresult['folders']);
2692                    if($remain) {
2693                        if($remain == $limit)
2694                            $offset -= $totalFolders;
2695                        else
2696                            $offset = 0;
2697
2698                        $searchQuery .= $orderbyQuery;
2699
2700                        if($limit)
2701                            $searchQuery .= " LIMIT ".$limit." OFFSET ".$offset;
2702
2703                        // Send the complete search query to the database.
2704                        $resArr = $this->db->getResultArray($searchQuery);
2705                        if($resArr === false)
2706                            return false;
2707                    } else {
2708                        $resArr = array();
2709                    }
2710                } else {
2711                    $searchQuery .= $orderbyQuery;
2712
2713                    // Send the complete search query to the database.
2714                    $resArr = $this->db->getResultArray($searchQuery);
2715                    if($resArr === false)
2716                        return false;
2717                }
2718
2719                // ------------------- Ausgabe der Ergebnisse ----------------------------
2720                $numResults = count($resArr);
2721                if ($numResults == 0) {
2722                    $docresult = array('totalDocs'=>$totalDocs, 'docs'=>array());
2723                } else {
2724                    foreach ($resArr as $docArr) {
2725                        $docs[] = $this->getDocument($docArr['id']);
2726                    }
2727                    /** @noinspection PhpUndefinedVariableInspection */
2728                    $docresult = array('totalDocs'=>$totalDocs, 'docs'=>$docs);
2729                }
2730            } else {
2731                $docresult = array('totalDocs'=>0, 'docs'=>array());
2732            }
2733        } else {
2734            $docresult = array('totalDocs'=>0, 'docs'=>array());
2735        }
2736
2737        if($limit) {
2738            $totalPages = (integer)(($totalDocs+$totalFolders)/$limit);
2739            if ((($totalDocs+$totalFolders)%$limit) > 0) {
2740                $totalPages++;
2741            }
2742        } else {
2743            $totalPages = 1;
2744        }
2745
2746        return array_merge($docresult, $folderresult, array('totalPages'=>$totalPages));
2747    } /* }}} */
2748
2749    /**
2750     * Return a folder by its id
2751     *
2752     * This function retrieves a folder from the database by its id.
2753     *
2754     * @param integer $id internal id of folder
2755     * @return SeedDMS_Core_Folder instance of SeedDMS_Core_Folder or false
2756     */
2757    function getFolder($id) { /* {{{ */
2758        $classname = $this->classnames['folder'];
2759        return $classname::getInstance($id, $this);
2760    } /* }}} */
2761
2762    /**
2763     * Return a folder by its name
2764     *
2765     * This function retrieves a folder from the database by its name. The
2766     * search covers the whole database. If
2767     * the parameter $folder is not null, it will search for the name
2768     * only within this parent folder. It will not be done recursively.
2769     *
2770     * @param string $name name of the folder
2771     * @param SeedDMS_Core_Folder $folder parent folder
2772     * @return SeedDMS_Core_Folder|boolean found folder or false
2773     */
2774    function getFolderByName($name, $folder=null) { /* {{{ */
2775        $name = trim($name);
2776        $classname = $this->classnames['folder'];
2777        return $classname::getInstanceByName($name, $folder, $this);
2778    } /* }}} */
2779
2780    /**
2781     * Returns a list of folders and error message not linked in the tree
2782     *
2783     * This function checks all folders in the database.
2784     *
2785     * @return array|bool
2786     */
2787    function checkFolders() { /* {{{ */
2788        $queryStr = "SELECT * FROM `tblFolders`";
2789        $resArr = $this->db->getResultArray($queryStr);
2790
2791        if (is_bool($resArr) && $resArr === false)
2792            return false;
2793
2794        $cache = array();
2795        foreach($resArr as $rec) {
2796            $cache[$rec['id']] = array('name'=>$rec['name'], 'parent'=>$rec['parent'], 'folderList'=>$rec['folderList']);
2797        }
2798        $errors = array();
2799        foreach($cache as $id=>$rec) {
2800            if(!array_key_exists($rec['parent'], $cache) && $rec['parent'] != 0) {
2801                $errors[$id] = array('id'=>$id, 'name'=>$rec['name'], 'parent'=>$rec['parent'], 'msg'=>'Missing parent');
2802            }
2803            if(!isset($errors[$id]))    {
2804                /* Create the real folderList and compare it with the stored folderList */
2805                $parent = $rec['parent'];
2806                $fl = [];
2807                while($parent) {
2808                    array_unshift($fl, $parent);
2809                    $parent = $cache[$parent]['parent'];
2810                }
2811                if($fl)
2812                    $flstr = ':'.implode(':', $fl).':';
2813                else
2814                    $flstr = '';
2815                if($flstr != $rec['folderList'])
2816                    $errors[$id] = array('id'=>$id, 'name'=>$rec['name'], 'parent'=>$rec['parent'], 'msg'=>'Wrong folder list '.$flstr.'!='.$rec['folderList']);
2817            }
2818            if(!isset($errors[$id]))    {
2819                /* This is the old insufficient test which will most likely not be called
2820                 * anymore, because the check for a wrong folder list will cache a folder
2821                 * list problem anyway.
2822                 */
2823                $tmparr = explode(':', $rec['folderList']);
2824                array_shift($tmparr);
2825                if(count($tmparr) != count(array_unique($tmparr))) {
2826                    $errors[$id] = array('id'=>$id, 'name'=>$rec['name'], 'parent'=>$rec['parent'], 'msg'=>'Duplicate entry in folder list ('.$rec['folderList'].')');
2827                }
2828            }
2829        }
2830
2831        return $errors;
2832    } /* }}} */
2833
2834    /**
2835     * Returns a list of documents and error message not linked in the tree
2836     *
2837     * This function checks all documents in the database.
2838     *
2839     * @return array|bool
2840     */
2841    function checkDocuments() { /* {{{ */
2842        $queryStr = "SELECT * FROM `tblFolders`";
2843        $resArr = $this->db->getResultArray($queryStr);
2844
2845        if (is_bool($resArr) && $resArr === false)
2846            return false;
2847
2848        $fcache = array();
2849        foreach($resArr as $rec) {
2850            $fcache[$rec['id']] = array('name'=>$rec['name'], 'parent'=>$rec['parent'], 'folderList'=>$rec['folderList']);
2851        }
2852
2853        $queryStr = "SELECT * FROM `tblDocuments`";
2854        $resArr = $this->db->getResultArray($queryStr);
2855
2856        if (is_bool($resArr) && $resArr === false)
2857            return false;
2858
2859        $dcache = array();
2860        foreach($resArr as $rec) {
2861            $dcache[$rec['id']] = array('name'=>$rec['name'], 'parent'=>$rec['folder'], 'folderList'=>$rec['folderList']);
2862        }
2863        $errors = array();
2864        foreach($dcache as $id=>$rec) {
2865            if(!array_key_exists($rec['parent'], $fcache) && $rec['parent'] != 0) {
2866                $errors[$id] = array('id'=>$id, 'name'=>$rec['name'], 'parent'=>$rec['parent'], 'msg'=>'Missing parent');
2867            }
2868            if(!isset($errors[$id]))    {
2869                /* Create the real folderList and compare it with the stored folderList */
2870                $parent = $rec['parent'];
2871                $fl = [];
2872                while($parent) {
2873                    array_unshift($fl, $parent);
2874                    $parent = $fcache[$parent]['parent'];
2875                }
2876                if($fl)
2877                    $flstr = ':'.implode(':', $fl).':';
2878                if($flstr != $rec['folderList'])
2879                    $errors[$id] = array('id'=>$id, 'name'=>$rec['name'], 'parent'=>$rec['parent'], 'msg'=>'Wrong folder list '.$flstr.'!='.$rec['folderList']);
2880            }
2881            if(!isset($errors[$id]))    {
2882                $tmparr = explode(':', $rec['folderList']);
2883                array_shift($tmparr);
2884                if(count($tmparr) != count(array_unique($tmparr))) {
2885                    $errors[$id] = array('id'=>$id, 'name'=>$rec['name'], 'parent'=>$rec['parent'], 'msg'=>'Duplicate entry in folder list ('.$rec['folderList'].'');
2886                }
2887            }
2888        }
2889
2890        return $errors;
2891    } /* }}} */
2892
2893    /**
2894     * Return a user by its id
2895     *
2896     * This function retrieves a user from the database by its id.
2897     *
2898     * @param integer $id internal id of user
2899     * @return SeedDMS_Core_User|boolean instance of {@link SeedDMS_Core_User} or false
2900     */
2901    function getUser($id) { /* {{{ */
2902        if($this->usecache && isset($this->cache['users'][$id])) {
2903            return $this->cache['users'][$id];
2904        }
2905        $classname = $this->classnames['user'];
2906        $user = $classname::getInstance($id, $this);
2907        if($this->usecache)
2908            $this->cache['users'][$id] = $user;
2909        return $user;
2910    } /* }}} */
2911
2912    /**
2913     * Return a user by its login
2914     *
2915     * This function retrieves a user from the database by its login.
2916     * If the second optional parameter $email is not empty, the user must
2917     * also have the given email.
2918     *
2919     * @param string $login internal login of user
2920     * @param string $email email of user
2921     * @return object instance of {@link SeedDMS_Core_User} or false
2922     */
2923    function getUserByLogin($login, $email='') { /* {{{ */
2924        $classname = $this->classnames['user'];
2925        return $classname::getInstance($login, $this, 'name', $email);
2926    } /* }}} */
2927
2928    /**
2929     * Return a user by its email
2930     *
2931     * This function retrieves a user from the database by its email.
2932     * It is needed when the user requests a new password.
2933     *
2934     * @param integer $email email address of user
2935     * @return object instance of {@link SeedDMS_Core_User} or false
2936     */
2937    function getUserByEmail($email) { /* {{{ */
2938        $classname = $this->classnames['user'];
2939        return $classname::getInstance($email, $this, 'email');
2940    } /* }}} */
2941
2942    /**
2943     * Return list of all users
2944     *
2945     * @param string $orderby
2946     * @return array of instances of <a href='psi_element://SeedDMS_Core_User'>SeedDMS_Core_User</a> or false
2947     * or false
2948     */
2949    function getAllUsers($orderby = '') { /* {{{ */
2950        $classname = $this->classnames['user'];
2951        return $classname::getAllInstances($orderby, $this);
2952    } /* }}} */
2953
2954    /**
2955     * Add a new user
2956     *
2957     * @param string $login login name
2958     * @param string $pwd password of new user
2959     * @param $fullName
2960     * @param string $email Email of new user
2961     * @param string $language language of new user
2962     * @param $theme
2963     * @param string $comment comment of new user
2964     * @param int|string $role role of new user (can be 0=normal, 1=admin, 2=guest)
2965     * @param integer $isHidden hide user in all lists, if this is set login
2966     *        is still allowed
2967     * @param integer $isDisabled disable user and prevent login
2968     * @param string $pwdexpiration
2969     * @param int $quota
2970     * @param null $homefolder
2971     * @return bool|SeedDMS_Core_User
2972     */
2973    function addUser($login, $pwd, $fullName, $email, $language, $theme, $comment, $role='3', $isHidden=0, $isDisabled=0, $pwdexpiration='', $quota=0, $homefolder=null) { /* {{{ */
2974        $db = $this->db;
2975        if (is_object($this->getUserByLogin($login))) {
2976            return false;
2977        }
2978        if(!is_object($role)) {
2979            if($role == '')
2980                $role = SeedDMS_Core_Role::getInstance(3, $this);
2981            else
2982                $role = SeedDMS_Core_Role::getInstance($role, $this);
2983        }
2984        if(trim($pwdexpiration) == '' || trim($pwdexpiration) == 'never') {
2985            $pwdexpiration = 'NULL';
2986        } elseif(trim($pwdexpiration) == 'now') {
2987            $pwdexpiration = $db->qstr(date('Y-m-d H:i:s'));
2988        } else {
2989            $pwdexpiration = $db->qstr($pwdexpiration);
2990        }
2991        $queryStr = "INSERT INTO `tblUsers` (`login`, `pwd`, `fullName`, `email`, `language`, `theme`, `comment`, `role`, `hidden`, `disabled`, `pwdExpiration`, `quota`, `homefolder`) VALUES (".$db->qstr($login).", ".$db->qstr($pwd).", ".$db->qstr($fullName).", ".$db->qstr($email).", '".$language."', '".$theme."', ".$db->qstr($comment).", '".intval($role->getId())."', '".intval($isHidden)."', '".intval($isDisabled)."', ".$pwdexpiration.", '".intval($quota)."', ".($homefolder ? intval($homefolder) : "NULL").")";
2992        $res = $this->db->getResult($queryStr);
2993        if (!$res)
2994            return false;
2995
2996        $user = $this->getUser($this->db->getInsertID('tblUsers'));
2997
2998        /* Check if 'onPostAddUser' callback is set */
2999        if(isset($this->callbacks['onPostAddUser'])) {
3000            foreach($this->callbacks['onPostAddUser'] as $callback) {
3001                /** @noinspection PhpStatementHasEmptyBodyInspection */
3002                if(!call_user_func($callback[0], $callback[1], $user)) {
3003                }
3004            }
3005        }
3006
3007        return $user;
3008    } /* }}} */
3009
3010    /**
3011     * Get a group by its id
3012     *
3013     * @param integer $id id of group
3014     * @return SeedDMS_Core_Group|boolean group or false if no group was found
3015     */
3016    function getGroup($id) { /* {{{ */
3017        if($this->usecache && isset($this->cache['groups'][$id])) {
3018            return $this->cache['groups'][$id];
3019        }
3020        $classname = $this->classnames['group'];
3021        $group = $classname::getInstance($id, $this, '');
3022        if($this->usecache)
3023            $this->cache['groups'][$id] = $group;
3024        return $group;
3025    } /* }}} */
3026
3027    /**
3028     * Get a group by its name
3029     *
3030     * @param string $name name of group
3031     * @return SeedDMS_Core_Group|boolean group or false if no group was found
3032     */
3033    function getGroupByName($name) { /* {{{ */
3034        $name = trim($name);
3035        $classname = $this->classnames['group'];
3036        return $classname::getInstance($name, $this, 'name');
3037    } /* }}} */
3038
3039    /**
3040     * Get a list of all groups
3041     *
3042     * @return SeedDMS_Core_Group[] array of instances of {@link SeedDMS_Core_Group}
3043     */
3044    function getAllGroups() { /* {{{ */
3045        $classname = $this->classnames['group'];
3046        return $classname::getAllInstances('name', $this);
3047    } /* }}} */
3048
3049    /**
3050     * Create a new user group
3051     *
3052     * @param string $name name of group
3053     * @param string $comment comment of group
3054     * @return SeedDMS_Core_Group|boolean instance of {@link SeedDMS_Core_Group} or false in
3055     *         case of an error.
3056     */
3057    function addGroup($name, $comment) { /* {{{ */
3058        $name = trim($name);
3059        if (is_object($this->getGroupByName($name))) {
3060            return false;
3061        }
3062
3063        $queryStr = "INSERT INTO `tblGroups` (`name`, `comment`) VALUES (".$this->db->qstr($name).", ".$this->db->qstr($comment).")";
3064        if (!$this->db->getResult($queryStr))
3065            return false;
3066
3067        $group = $this->getGroup($this->db->getInsertID('tblGroups'));
3068
3069        /* Check if 'onPostAddGroup' callback is set */
3070        if(isset($this->callbacks['onPostAddGroup'])) {
3071            foreach($this->callbacks['onPostAddGroup'] as $callback) {
3072                /** @noinspection PhpStatementHasEmptyBodyInspection */
3073                if(!call_user_func($callback[0], $callback[1], $group)) {
3074                }
3075            }
3076        }
3077
3078        return $group;
3079    } /* }}} */
3080
3081    /**
3082     * Get a role by its id
3083     *
3084     * @param integer $id id of role
3085     * @return object/boolean role or false if no role was found
3086     */
3087    function getRole($id) { /* {{{ */
3088        $classname = $this->classnames['role'];
3089        return $classname::getInstance($id, $this);
3090    } /* }}} */
3091
3092    /**
3093     * Get a role by its name
3094     *
3095     * @param integer $name name of role
3096     * @return object/boolean role or false if no role was found
3097     */
3098    function getRoleByName($name) { /* {{{ */
3099        $classname = $this->classnames['role'];
3100        return $classname::getInstance($name, $this, 'name');
3101    } /* }}} */
3102
3103    /**
3104     * Return list of all roles
3105     *
3106     * @return array of instances of {@link SeedDMS_Core_Role} or false
3107     */
3108    function getAllRoles($orderby = '') { /* {{{ */
3109        $classname = $this->classnames['role'];
3110        return $classname::getAllInstances($orderby, $this);
3111    } /* }}} */
3112
3113    /**
3114     * Create a new role
3115     *
3116     * @param string $name name of role
3117     * @return object/boolean instance of {@link SeedDMS_Core_Role} or false in
3118     *         case of an error.
3119     */
3120    function addRole($name, $role) { /* {{{ */
3121        if (is_object($this->getRoleByName($name))) {
3122            return false;
3123        }
3124
3125        $queryStr = "INSERT INTO `tblRoles` (`name`, `role`) VALUES (".$this->db->qstr($name).", ".$role.")";
3126        if (!$this->db->getResult($queryStr))
3127            return false;
3128
3129        return $this->getRole($this->db->getInsertID('tblRoles'));
3130    } /* }}} */
3131
3132    /**
3133     * Get a transmittal by its id
3134     *
3135     * @param integer $id id of transmittal
3136     * @return object/boolean transmittal or false if no group was found
3137     */
3138    function getTransmittal($id) { /* {{{ */
3139        $classname = $this->classnames['transmittal'];
3140        return $classname::getInstance($id, $this, '');
3141    } /* }}} */
3142
3143    /**
3144     * Get a transmittal by its name
3145     *
3146     * @param string $name name of transmittal
3147     * @return object/boolean transmittal or false if no group was found
3148     */
3149    function getTransmittalByName($name) { /* {{{ */
3150        $classname = $this->classnames['transmittal'];
3151        return $classname::getInstance($name, $this, 'name');
3152    } /* }}} */
3153
3154    /**
3155     * Return list of all transmittals
3156     *
3157     * @return array of instances of {@link SeedDMS_Core_Transmittal} or false
3158     */
3159    function getAllTransmittals($user=null, $orderby = '') { /* {{{ */
3160        $classname = $this->classnames['transmittal'];
3161        return $classname::getAllInstances($user, $orderby, $this);
3162    } /* }}} */
3163
3164    /**
3165     * Create a new transmittal
3166     *
3167     * @param string $name name of group
3168     * @param string $comment comment of group
3169     * @param object $user user this transmittal belongs to
3170     * @return object/boolean instance of {@link SeedDMS_Core_Transmittal} or
3171     *         false in case of an error.
3172     */
3173    function addTransmittal($name, $comment, $user) { /* {{{ */
3174        if (is_object($this->getTransmittalByName($name))) {
3175            return false;
3176        }
3177
3178        $queryStr = "INSERT INTO `tblTransmittals` (`name`, `comment`, `userID`) VALUES (".$this->db->qstr($name).", ".$this->db->qstr($comment).", ".$user->getID().")";
3179        if (!$this->db->getResult($queryStr))
3180            return false;
3181
3182        return $this->getTransmittal($this->db->getInsertID('tblTransmittals'));
3183    } /* }}} */
3184
3185    function getKeywordCategory($id) { /* {{{ */
3186        if (!is_numeric($id) || $id < 1)
3187            return false;
3188
3189        $queryStr = "SELECT * FROM `tblKeywordCategories` WHERE `id` = " . (int) $id;
3190        $resArr = $this->db->getResultArray($queryStr);
3191        if (is_bool($resArr) && !$resArr)
3192            return false;
3193        if (count($resArr) != 1)
3194            return null;
3195
3196        $resArr = $resArr[0];
3197        $cat = new SeedDMS_Core_Keywordcategory($resArr["id"], $resArr["owner"], $resArr["name"]);
3198        $cat->setDMS($this);
3199        return $cat;
3200    } /* }}} */
3201
3202    function getKeywordCategoryByName($name, $userID) { /* {{{ */
3203        if (!is_numeric($userID) || $userID < 1)
3204            return false;
3205        $name = trim($name);
3206        $queryStr = "SELECT * FROM `tblKeywordCategories` WHERE `name` = " . $this->db->qstr($name) . " AND `owner` = " . (int) $userID;
3207        $resArr = $this->db->getResultArray($queryStr);
3208        if (is_bool($resArr) && !$resArr)
3209            return false;
3210        if (count($resArr) != 1)
3211            return null;
3212
3213        $resArr = $resArr[0];
3214        $cat = new SeedDMS_Core_Keywordcategory($resArr["id"], $resArr["owner"], $resArr["name"]);
3215        $cat->setDMS($this);
3216        return $cat;
3217    } /* }}} */
3218
3219    function getAllKeywordCategories($userIDs = array()) { /* {{{ */
3220        $queryStr = "SELECT * FROM `tblKeywordCategories`";
3221        /* Ensure $userIDs() will only contain integers > 0 */
3222        $userIDs = array_filter(array_unique(array_map('intval', $userIDs)), function($a) {return $a > 0;});
3223        if ($userIDs) {
3224            $queryStr .= " WHERE `owner` IN (".implode(',', $userIDs).")";
3225        }
3226
3227        $resArr = $this->db->getResultArray($queryStr);
3228        if (is_bool($resArr) && !$resArr)
3229            return false;
3230
3231        $categories = array();
3232        foreach ($resArr as $row) {
3233            $cat = new SeedDMS_Core_KeywordCategory($row["id"], $row["owner"], $row["name"]);
3234            $cat->setDMS($this);
3235            array_push($categories, $cat);
3236        }
3237
3238        return $categories;
3239    } /* }}} */
3240
3241    /**
3242     * This function should be replaced by getAllKeywordCategories()
3243     *
3244     * @param $userID
3245     * @return SeedDMS_Core_KeywordCategory[]|bool
3246     */
3247    function getAllUserKeywordCategories($userID) { /* {{{ */
3248        if (!is_numeric($userID) || $userID < 1)
3249            return false;
3250        return self::getAllKeywordCategories([$userID]);
3251    } /* }}} */
3252
3253    function addKeywordCategory($userID, $name) { /* {{{ */
3254        if (!is_numeric($userID) || $userID < 1)
3255            return false;
3256        $name = trim($name);
3257        if(!$name)
3258            return false;
3259        if (is_object($this->getKeywordCategoryByName($name, $userID))) {
3260            return false;
3261        }
3262        $queryStr = "INSERT INTO `tblKeywordCategories` (`owner`, `name`) VALUES (".(int) $userID.", ".$this->db->qstr($name).")";
3263        if (!$this->db->getResult($queryStr))
3264            return false;
3265
3266        $category = $this->getKeywordCategory($this->db->getInsertID('tblKeywordCategories'));
3267
3268        /* Check if 'onPostAddKeywordCategory' callback is set */
3269        if(isset($this->callbacks['onPostAddKeywordCategory'])) {
3270            foreach($this->callbacks['onPostAddKeywordCategory'] as $callback) {
3271                /** @noinspection PhpStatementHasEmptyBodyInspection */
3272                if(!call_user_func($callback[0], $callback[1], $category)) {
3273                }
3274            }
3275        }
3276
3277        return $category;
3278    } /* }}} */
3279
3280    function getDocumentCategory($id) { /* {{{ */
3281        if (!is_numeric($id) || $id < 1)
3282            return false;
3283
3284        $queryStr = "SELECT * FROM `tblCategory` WHERE `id` = " . (int) $id;
3285        $resArr = $this->db->getResultArray($queryStr);
3286        if (is_bool($resArr) && !$resArr)
3287            return false;
3288        if (count($resArr) != 1)
3289            return null;
3290
3291        $resArr = $resArr[0];
3292        $cat = new SeedDMS_Core_DocumentCategory($resArr["id"], $resArr["name"]);
3293        $cat->setDMS($this);
3294        return $cat;
3295    } /* }}} */
3296
3297    function getDocumentCategories() { /* {{{ */
3298        $queryStr = "SELECT * FROM `tblCategory` order by `name`";
3299
3300        $resArr = $this->db->getResultArray($queryStr);
3301        if (is_bool($resArr) && !$resArr)
3302            return false;
3303
3304        $categories = array();
3305        foreach ($resArr as $row) {
3306            $cat = new SeedDMS_Core_DocumentCategory($row["id"], $row["name"]);
3307            $cat->setDMS($this);
3308            array_push($categories, $cat);
3309        }
3310
3311        return $categories;
3312    } /* }}} */
3313
3314    /**
3315     * Get a category by its name
3316     *
3317     * The name of a category is by default unique.
3318     *
3319     * @param string $name human readable name of category
3320     * @return SeedDMS_Core_DocumentCategory|boolean instance of {@link SeedDMS_Core_DocumentCategory}
3321     */
3322    function getDocumentCategoryByName($name) { /* {{{ */
3323        $name = trim($name);
3324        if (!$name) return false;
3325
3326        $queryStr = "SELECT * FROM `tblCategory` WHERE `name`=".$this->db->qstr($name);
3327        $resArr = $this->db->getResultArray($queryStr);
3328        if (!$resArr)
3329            return false;
3330
3331        $row = $resArr[0];
3332        $cat = new SeedDMS_Core_DocumentCategory($row["id"], $row["name"]);
3333        $cat->setDMS($this);
3334
3335        return $cat;
3336    } /* }}} */
3337
3338    function addDocumentCategory($name) { /* {{{ */
3339        $name = trim($name);
3340        if(!$name)
3341            return false;
3342        if (is_object($this->getDocumentCategoryByName($name))) {
3343            return false;
3344        }
3345        $queryStr = "INSERT INTO `tblCategory` (`name`) VALUES (".$this->db->qstr($name).")";
3346        if (!$this->db->getResult($queryStr))
3347            return false;
3348
3349        $category = $this->getDocumentCategory($this->db->getInsertID('tblCategory'));
3350
3351        /* Check if 'onPostAddDocumentCategory' callback is set */
3352        if(isset($this->callbacks['onPostAddDocumentCategory'])) {
3353            foreach($this->callbacks['onPostAddDocumentCategory'] as $callback) {
3354                /** @noinspection PhpStatementHasEmptyBodyInspection */
3355                if(!call_user_func($callback[0], $callback[1], $category)) {
3356                }
3357            }
3358        }
3359
3360        return $category;
3361    } /* }}} */
3362
3363    /**
3364     * Get all notifications for a group
3365     *
3366     * deprecated: User {@link SeedDMS_Core_Group::getNotifications()}
3367     *
3368     * @param object $group group for which notifications are to be retrieved
3369     * @param integer $type type of item (T_DOCUMENT or T_FOLDER)
3370     * @return array array of notifications
3371     */
3372    function getNotificationsByGroup($group, $type=0) { /* {{{ */
3373        return $group->getNotifications($type);
3374    } /* }}} */
3375
3376    /**
3377     * Get all notifications for a user
3378     *
3379     * deprecated: User {@link SeedDMS_Core_User::getNotifications()}
3380     *
3381     * @param object $user user for which notifications are to be retrieved
3382     * @param integer $type type of item (T_DOCUMENT or T_FOLDER)
3383     * @return array array of notifications
3384     */
3385    function getNotificationsByUser($user, $type=0) { /* {{{ */
3386        return $user->getNotifications($type);
3387    } /* }}} */
3388
3389    /**
3390     * Create a token to request a new password.
3391     * This function will not delete the password but just creates an entry
3392     * in tblUserRequestPassword indicating a password request.
3393     *
3394     * @param SeedDMS_Core_User $user
3395     * @return string|boolean hash value of false in case of an error
3396     */
3397    function createPasswordRequest($user) { /* {{{ */
3398        $lenght = 32;
3399        if (function_exists("random_bytes")) {
3400            $bytes = random_bytes((int) ceil($lenght / 2));
3401        } elseif (function_exists("openssl_random_pseudo_bytes")) {
3402            $bytes = openssl_random_pseudo_bytes(ceil($lenght / 2));
3403        } else {
3404            return false;
3405        }
3406        $hash = bin2hex($bytes);
3407        $queryStr = "INSERT INTO `tblUserPasswordRequest` (`userID`, `hash`, `date`) VALUES (" . $user->getId() . ", " . $this->db->qstr($hash) .", ".$this->db->getCurrentDatetime().")";
3408        $resArr = $this->db->getResult($queryStr);
3409        if (is_bool($resArr) && !$resArr) return false;
3410        return $hash;
3411
3412    } /* }}} */
3413
3414    /**
3415     * Check if hash for a password request is valid.
3416     * This function searches a previously create password request and
3417     * returns the user.
3418     *
3419     * @param string $hash
3420     * @return bool|SeedDMS_Core_User
3421     */
3422    function checkPasswordRequest($hash) { /* {{{ */
3423        /* Get the password request from the database */
3424        $queryStr = "SELECT * FROM `tblUserPasswordRequest` WHERE `hash`=".$this->db->qstr($hash);
3425        $resArr = $this->db->getResultArray($queryStr);
3426        if (is_bool($resArr) && !$resArr)
3427            return false;
3428
3429        if (count($resArr) != 1)
3430            return false;
3431        $resArr = $resArr[0];
3432
3433        return $this->getUser($resArr['userID']);
3434
3435    } /* }}} */
3436
3437    /**
3438     * Delete a password request
3439     *
3440     * @param string $hash
3441     * @return bool
3442     */
3443    function deletePasswordRequest($hash) { /* {{{ */
3444        /* Delete the request, so nobody can use it a second time */
3445        $queryStr = "DELETE FROM `tblUserPasswordRequest` WHERE `hash`=".$this->db->qstr($hash);
3446        if (!$this->db->getResult($queryStr))
3447            return false;
3448        return true;
3449    } /* }}} */
3450
3451    /**
3452     * Return a attribute definition by its id
3453     *
3454     * This function retrieves a attribute definitionr from the database by
3455     * its id.
3456     *
3457     * @param integer $id internal id of attribute defintion
3458     * @return bool|SeedDMS_Core_AttributeDefinition or false
3459     */
3460    function getAttributeDefinition($id) { /* {{{ */
3461        if (!is_numeric($id) || $id < 1)
3462            return false;
3463
3464        $queryStr = "SELECT * FROM `tblAttributeDefinitions` WHERE `id` = " . (int) $id;
3465        $resArr = $this->db->getResultArray($queryStr);
3466
3467        if (is_bool($resArr) && $resArr == false)
3468            return false;
3469        if (count($resArr) != 1)
3470            return null;
3471
3472        $resArr = $resArr[0];
3473
3474        $attrdef = new SeedDMS_Core_AttributeDefinition($resArr["id"], $resArr["name"], (int) $resArr["objtype"], (int) $resArr["type"], $resArr["multiple"], $resArr["minvalues"], $resArr["maxvalues"], $resArr["valueset"], $resArr["regex"]);
3475        $attrdef->setDMS($this);
3476        return $attrdef;
3477    } /* }}} */
3478
3479    /**
3480     * Return a attribute definition by its name
3481     *
3482     * This function retrieves an attribute def. from the database by its name.
3483     *
3484     * @param string $name internal name of attribute def.
3485     * @return SeedDMS_Core_AttributeDefinition|boolean instance of {@link SeedDMS_Core_AttributeDefinition} or false
3486     */
3487    function getAttributeDefinitionByName($name) { /* {{{ */
3488        $name = trim($name);
3489        if (!$name) return false;
3490
3491        $queryStr = "SELECT * FROM `tblAttributeDefinitions` WHERE `name` = " . $this->db->qstr($name);
3492        $resArr = $this->db->getResultArray($queryStr);
3493
3494        if (is_bool($resArr) && $resArr == false)
3495            return false;
3496        if (count($resArr) != 1)
3497            return null;
3498
3499        $resArr = $resArr[0];
3500
3501        $attrdef = new SeedDMS_Core_AttributeDefinition($resArr["id"], $resArr["name"], (int) $resArr["objtype"], (int) $resArr["type"], $resArr["multiple"], $resArr["minvalues"], $resArr["maxvalues"], $resArr["valueset"], $resArr["regex"]);
3502        $attrdef->setDMS($this);
3503        return $attrdef;
3504    } /* }}} */
3505
3506    /**
3507     * Return list of all attributes definitions
3508     *
3509     * @param integer|array $objtype select those attributes defined for an object type
3510     * @param integer|array $type select those attributes defined for a type
3511     * @return bool|SeedDMS_Core_AttributeDefinition[] of instances of <a href='psi_element://SeedDMS_Core_AttributeDefinition'>SeedDMS_Core_AttributeDefinition</a> or false
3512     * or false
3513     */
3514    function getAllAttributeDefinitions($objtype=0, $type=0) { /* {{{ */
3515        $queryStr = "SELECT * FROM `tblAttributeDefinitions`";
3516        if($objtype || $type) {
3517            $queryStr .= ' WHERE ';
3518            if($objtype) {
3519                if(is_array($objtype))
3520                    $queryStr .= '`objtype` in (\''.implode("','", $objtype).'\')';
3521                else
3522                    $queryStr .= '`objtype`='.intval($objtype);
3523            }
3524            if($objtype && $type) {
3525                $queryStr .= ' AND ';
3526            }
3527            if($type) {
3528                if(is_array($type))
3529                    $queryStr .= '`type` in (\''.implode("','", $type).'\')';
3530                else
3531                    $queryStr .= '`type`='.intval($type);
3532            }
3533        }
3534        $queryStr .= ' ORDER BY `name`';
3535        $resArr = $this->db->getResultArray($queryStr);
3536
3537        if (is_bool($resArr) && $resArr == false)
3538            return false;
3539
3540        /** @var SeedDMS_Core_AttributeDefinition[] $attrdefs */
3541        $attrdefs = array();
3542
3543        for ($i = 0; $i < count($resArr); $i++) {
3544            $attrdef = new SeedDMS_Core_AttributeDefinition($resArr[$i]["id"], $resArr[$i]["name"], (int) $resArr[$i]["objtype"], (int) $resArr[$i]["type"], $resArr[$i]["multiple"], $resArr[$i]["minvalues"], $resArr[$i]["maxvalues"], $resArr[$i]["valueset"], $resArr[$i]["regex"]);
3545            $attrdef->setDMS($this);
3546            $attrdefs[$i] = $attrdef;
3547        }
3548
3549        return $attrdefs;
3550    } /* }}} */
3551
3552    /**
3553     * Add a new attribute definition
3554     *
3555     * @param string $name name of attribute
3556     * @param $objtype
3557     * @param string $type type of attribute
3558     * @param bool|int $multiple set to 1 if attribute has multiple attributes
3559     * @param integer $minvalues minimum number of values
3560     * @param integer $maxvalues maximum number of values if multiple is set
3561     * @param string $valueset list of allowed values (csv format)
3562     * @param string $regex
3563     * @return bool|SeedDMS_Core_User
3564     */
3565    function addAttributeDefinition($name, $objtype, $type, $multiple=0, $minvalues=0, $maxvalues=1, $valueset='', $regex='') { /* {{{ */
3566        $name = trim($name);
3567        if(!$name)
3568            return false;
3569        if (is_object($this->getAttributeDefinitionByName($name))) {
3570            return false;
3571        }
3572        if($objtype < SeedDMS_Core_AttributeDefinition::objtype_all || $objtype > SeedDMS_Core_AttributeDefinition::objtype_documentcontent)
3573            return false;
3574        if(!$type)
3575            return false;
3576        if(trim($valueset)) {
3577            $valuesetarr = array_map('trim', explode($valueset[0], substr($valueset, 1)));
3578            $valueset = $valueset[0].implode($valueset[0], $valuesetarr);
3579        } else {
3580            $valueset = '';
3581        }
3582        $queryStr = "INSERT INTO `tblAttributeDefinitions` (`name`, `objtype`, `type`, `multiple`, `minvalues`, `maxvalues`, `valueset`, `regex`) VALUES (".$this->db->qstr($name).", ".intval($objtype).", ".intval($type).", ".intval($multiple).", ".intval($minvalues).", ".intval($maxvalues).", ".$this->db->qstr($valueset).", ".$this->db->qstr($regex).")";
3583        $res = $this->db->getResult($queryStr);
3584        if (!$res)
3585            return false;
3586
3587        return $this->getAttributeDefinition($this->db->getInsertID('tblAttributeDefinitions'));
3588    } /* }}} */
3589
3590    /**
3591     * Return list of all workflows
3592     *
3593     * @return SeedDMS_Core_Workflow[]|bool of instances of {@link SeedDMS_Core_Workflow} or false
3594     */
3595    function getAllWorkflows() { /* {{{ */
3596        $queryStr = "SELECT * FROM `tblWorkflows` ORDER BY `name`";
3597        $resArr = $this->db->getResultArray($queryStr);
3598
3599        if (is_bool($resArr) && $resArr == false)
3600            return false;
3601
3602        $queryStr = "SELECT * FROM `tblWorkflowStates` ORDER BY `name`";
3603        $ressArr = $this->db->getResultArray($queryStr);
3604
3605        if (is_bool($ressArr) && $ressArr == false)
3606            return false;
3607
3608        for ($i = 0; $i < count($ressArr); $i++) {
3609            $wkfstates[$ressArr[$i]["id"]] = new SeedDMS_Core_Workflow_State($ressArr[$i]["id"], $ressArr[$i]["name"], $ressArr[$i]["maxtime"], $ressArr[$i]["precondfunc"], $ressArr[$i]["documentstatus"]);
3610        }
3611
3612        /** @var SeedDMS_Core_Workflow[] $workflows */
3613        $workflows = array();
3614        for ($i = 0; $i < count($resArr); $i++) {
3615            /** @noinspection PhpUndefinedVariableInspection */
3616            $workflow = new SeedDMS_Core_Workflow($resArr[$i]["id"], $resArr[$i]["name"], $wkfstates[$resArr[$i]["initstate"]], $resArr[$i]["layoutdata"]);
3617            $workflow->setDMS($this);
3618            $workflows[$i] = $workflow;
3619        }
3620
3621        return $workflows;
3622    } /* }}} */
3623
3624    /**
3625     * Return workflow by its Id
3626     *
3627     * @param integer $id internal id of workflow
3628     * @return SeedDMS_Core_Workflow|bool of instances of {@link SeedDMS_Core_Workflow}, null if no workflow was found or false
3629     */
3630    function getWorkflow($id) { /* {{{ */
3631        if (!is_numeric($id) || $id < 1)
3632            return false;
3633
3634        $queryStr = "SELECT * FROM `tblWorkflows` WHERE `id`=".intval($id);
3635        $resArr = $this->db->getResultArray($queryStr);
3636
3637        if (is_bool($resArr) && $resArr == false)
3638            return false;
3639
3640        if(!$resArr)
3641            return null;
3642
3643        $initstate = $this->getWorkflowState($resArr[0]['initstate']);
3644
3645        $workflow = new SeedDMS_Core_Workflow($resArr[0]["id"], $resArr[0]["name"], $initstate, $resArr[0]["layoutdata"]);
3646        $workflow->setDMS($this);
3647
3648        return $workflow;
3649    } /* }}} */
3650
3651    /**
3652     * Return workflow by its name
3653     *
3654     * @param string $name name of workflow
3655     * @return SeedDMS_Core_Workflow|bool of instances of {@link SeedDMS_Core_Workflow} or null if no workflow was found or false
3656     */
3657    function getWorkflowByName($name) { /* {{{ */
3658        $name = trim($name);
3659        if (!$name) return false;
3660
3661        $queryStr = "SELECT * FROM `tblWorkflows` WHERE `name`=".$this->db->qstr($name);
3662        $resArr = $this->db->getResultArray($queryStr);
3663
3664        if (is_bool($resArr) && $resArr == false)
3665            return false;
3666
3667        if(!$resArr)
3668            return null;
3669
3670        $initstate = $this->getWorkflowState($resArr[0]['initstate']);
3671
3672        $workflow = new SeedDMS_Core_Workflow($resArr[0]["id"], $resArr[0]["name"], $initstate, $resArr[0]["layoutdata"]);
3673        $workflow->setDMS($this);
3674
3675        return $workflow;
3676    } /* }}} */
3677
3678    /**
3679     * Add a new workflow
3680     *
3681     * @param string $name name of workflow
3682     * @param SeedDMS_Core_Workflow_State $initstate initial state of workflow
3683     * @return bool|SeedDMS_Core_Workflow
3684     */
3685    function addWorkflow($name, $initstate) { /* {{{ */
3686        $db = $this->db;
3687        $name = trim($name);
3688        if(!$name)
3689            return false;
3690        if (is_object($this->getWorkflowByName($name))) {
3691            return false;
3692        }
3693        $queryStr = "INSERT INTO `tblWorkflows` (`name`, `initstate`) VALUES (".$db->qstr($name).", ".$initstate->getID().")";
3694        $res = $db->getResult($queryStr);
3695        if (!$res)
3696            return false;
3697
3698        return $this->getWorkflow($db->getInsertID('tblWorkflows'));
3699    } /* }}} */
3700
3701    /**
3702     * Return a workflow state by its id
3703     *
3704     * This function retrieves a workflow state from the database by its id.
3705     *
3706     * @param integer $id internal id of workflow state
3707     * @return bool|SeedDMS_Core_Workflow_State or false
3708     */
3709    function getWorkflowState($id) { /* {{{ */
3710        if (!is_numeric($id) || $id < 1)
3711            return false;
3712
3713        $queryStr = "SELECT * FROM `tblWorkflowStates` WHERE `id` = " . (int) $id;
3714        $resArr = $this->db->getResultArray($queryStr);
3715
3716        if (is_bool($resArr) && $resArr == false)
3717            return false;
3718
3719        if (count($resArr) != 1)
3720             return null;
3721
3722        $resArr = $resArr[0];
3723
3724        $state = new SeedDMS_Core_Workflow_State($resArr["id"], $resArr["name"], $resArr["maxtime"], $resArr["precondfunc"], $resArr["documentstatus"]);
3725        $state->setDMS($this);
3726        return $state;
3727    } /* }}} */
3728
3729    /**
3730     * Return workflow state by its name
3731     *
3732     * @param string $name name of workflow state
3733     * @return bool|SeedDMS_Core_Workflow_State or false
3734     */
3735    function getWorkflowStateByName($name) { /* {{{ */
3736        $name = trim($name);
3737        if (!$name) return false;
3738
3739        $queryStr = "SELECT * FROM `tblWorkflowStates` WHERE `name`=".$this->db->qstr($name);
3740        $resArr = $this->db->getResultArray($queryStr);
3741
3742        if (is_bool($resArr) && $resArr == false)
3743            return false;
3744
3745        if(!$resArr)
3746            return null;
3747
3748        $resArr = $resArr[0];
3749
3750        $state = new SeedDMS_Core_Workflow_State($resArr["id"], $resArr["name"], $resArr["maxtime"], $resArr["precondfunc"], $resArr["documentstatus"]);
3751        $state->setDMS($this);
3752
3753        return $state;
3754    } /* }}} */
3755
3756    /**
3757     * Return list of all workflow states
3758     *
3759     * @return SeedDMS_Core_Workflow_State[]|bool of instances of {@link SeedDMS_Core_Workflow_State} or false
3760     */
3761    function getAllWorkflowStates() { /* {{{ */
3762        $queryStr = "SELECT * FROM `tblWorkflowStates` ORDER BY `name`";
3763        $ressArr = $this->db->getResultArray($queryStr);
3764
3765        if (is_bool($ressArr) && $ressArr == false)
3766            return false;
3767
3768        $wkfstates = array();
3769        for ($i = 0; $i < count($ressArr); $i++) {
3770            $wkfstate = new SeedDMS_Core_Workflow_State($ressArr[$i]["id"], $ressArr[$i]["name"], $ressArr[$i]["maxtime"], $ressArr[$i]["precondfunc"], $ressArr[$i]["documentstatus"]);
3771            $wkfstate->setDMS($this);
3772            $wkfstates[$i] = $wkfstate;
3773        }
3774
3775        return $wkfstates;
3776    } /* }}} */
3777
3778    /**
3779     * Add new workflow state
3780     *
3781     * @param string $name name of workflow state
3782     * @param integer $docstatus document status when this state is reached
3783     * @return bool|SeedDMS_Core_Workflow_State
3784     */
3785    function addWorkflowState($name, $docstatus) { /* {{{ */
3786        $db = $this->db;
3787        $name = trim($name);
3788        if(!$name)
3789            return false;
3790        if (is_object($this->getWorkflowStateByName($name))) {
3791            return false;
3792        }
3793        $queryStr = "INSERT INTO `tblWorkflowStates` (`name`, `documentstatus`) VALUES (".$db->qstr($name).", ".(int) $docstatus.")";
3794        $res = $db->getResult($queryStr);
3795        if (!$res)
3796            return false;
3797
3798        return $this->getWorkflowState($db->getInsertID('tblWorkflowStates'));
3799    } /* }}} */
3800
3801    /**
3802     * Return a workflow action by its id
3803     *
3804     * This function retrieves a workflow action from the database by its id.
3805     *
3806     * @param integer $id internal id of workflow action
3807     * @return SeedDMS_Core_Workflow_Action|bool instance of {@link SeedDMS_Core_Workflow_Action} or false
3808     */
3809    function getWorkflowAction($id) { /* {{{ */
3810        if (!is_numeric($id) || $id < 1)
3811            return false;
3812
3813        $queryStr = "SELECT * FROM `tblWorkflowActions` WHERE `id` = " . (int) $id;
3814        $resArr = $this->db->getResultArray($queryStr);
3815
3816        if (is_bool($resArr) && $resArr == false)
3817            return false;
3818
3819        if (count($resArr) != 1)
3820             return null;
3821
3822        $resArr = $resArr[0];
3823
3824        $action = new SeedDMS_Core_Workflow_Action($resArr["id"], $resArr["name"]);
3825        $action->setDMS($this);
3826        return $action;
3827    } /* }}} */
3828
3829    /**
3830     * Return a workflow action by its name
3831     *
3832     * This function retrieves a workflow action from the database by its name.
3833     *
3834     * @param string $name name of workflow action
3835     * @return SeedDMS_Core_Workflow_Action|bool instance of {@link SeedDMS_Core_Workflow_Action} or false
3836     */
3837    function getWorkflowActionByName($name) { /* {{{ */
3838        $name = trim($name);
3839        if (!$name) return false;
3840
3841        $queryStr = "SELECT * FROM `tblWorkflowActions` WHERE `name` = " . $this->db->qstr($name);
3842        $resArr = $this->db->getResultArray($queryStr);
3843
3844        if (is_bool($resArr) && $resArr == false)
3845            return false;
3846
3847        if (count($resArr) != 1)
3848             return null;
3849
3850        $resArr = $resArr[0];
3851
3852        $action = new SeedDMS_Core_Workflow_Action($resArr["id"], $resArr["name"]);
3853        $action->setDMS($this);
3854        return $action;
3855    } /* }}} */
3856
3857    /**
3858     * Return list of workflow action
3859     *
3860     * @return SeedDMS_Core_Workflow_Action[]|bool list of instances of {@link SeedDMS_Core_Workflow_Action} or false
3861     */
3862    function getAllWorkflowActions() { /* {{{ */
3863        $queryStr = "SELECT * FROM `tblWorkflowActions`";
3864        $resArr = $this->db->getResultArray($queryStr);
3865
3866        if (is_bool($resArr) && $resArr == false)
3867            return false;
3868
3869        /** @var SeedDMS_Core_Workflow_Action[] $wkfactions */
3870        $wkfactions = array();
3871        for ($i = 0; $i < count($resArr); $i++) {
3872            $action = new SeedDMS_Core_Workflow_Action($resArr[$i]["id"], $resArr[$i]["name"]);
3873            $action->setDMS($this);
3874            $wkfactions[$i] = $action;
3875        }
3876
3877        return $wkfactions;
3878    } /* }}} */
3879
3880    /**
3881     * Add new workflow action
3882     *
3883     * @param string $name name of workflow action
3884     * @return SeedDMS_Core_Workflow_Action|bool
3885     */
3886    function addWorkflowAction($name) { /* {{{ */
3887        $db = $this->db;
3888        $name = trim($name);
3889        if(!$name)
3890            return false;
3891        if (is_object($this->getWorkflowActionByName($name))) {
3892            return false;
3893        }
3894        $queryStr = "INSERT INTO `tblWorkflowActions` (`name`) VALUES (".$db->qstr($name).")";
3895        $res = $db->getResult($queryStr);
3896        if (!$res)
3897            return false;
3898
3899        return $this->getWorkflowAction($db->getInsertID('tblWorkflowActions'));
3900    } /* }}} */
3901
3902    /**
3903     * Return a workflow transition by its id
3904     *
3905     * This function retrieves a workflow transition from the database by its id.
3906     *
3907     * @param integer $id internal id of workflow transition
3908     * @return SeedDMS_Core_Workflow_Transition|bool instance of {@link SeedDMS_Core_Workflow_Transition} or false
3909     */
3910    function getWorkflowTransition($id) { /* {{{ */
3911        if (!is_numeric($id))
3912            return false;
3913
3914        $queryStr = "SELECT * FROM `tblWorkflowTransitions` WHERE `id` = " . (int) $id;
3915        $resArr = $this->db->getResultArray($queryStr);
3916
3917        if (is_bool($resArr) && $resArr == false) return false;
3918        if (count($resArr) != 1) return false;
3919
3920        $resArr = $resArr[0];
3921
3922        $transition = new SeedDMS_Core_Workflow_Transition($resArr["id"], $this->getWorkflow($resArr["workflow"]), $this->getWorkflowState($resArr["state"]), $this->getWorkflowAction($resArr["action"]), $this->getWorkflowState($resArr["nextstate"]), $resArr["maxtime"]);
3923        $transition->setDMS($this);
3924        return $transition;
3925    } /* }}} */
3926
3927    /**
3928     * Return all documents waiting for or in reception
3929     *
3930     * This function retrieves all documents and its version which are waiting for
3931     * reception
3932     *
3933     * @return object instance of {@link SeedDMS_Core_DocumentContent} or false
3934     */
3935    function getDocumentsInReception() { /* {{{ */
3936        if (!$this->db->createTemporaryTable("ttreceiptid") || !$this->db->createTemporaryTable("ttcontentid")) {
3937            return false;
3938        }
3939        $queryStr =
3940            "SELECT `tblDocumentRecipients`.*, `tblDocumentReceiptLog`.`status` FROM `tblDocumentRecipients` LEFT JOIN `ttreceiptid` ON `tblDocumentRecipients`.`receiptID` = `ttreceiptid`.`receiptID` LEFT JOIN `tblDocumentReceiptLog` ON `ttreceiptid`.`maxLogID` = `tblDocumentReceiptLog`.`receiptLogID` LEFT JOIN `ttcontentid` ON `ttcontentid`.`maxVersion`=`tblDocumentRecipients`.`version` AND `ttcontentid`.`document`=`tblDocumentRecipients`.`documentID` WHERE `tblDocumentReceiptLog`.`status`=0 AND `ttcontentid`.`maxVersion` IS NOT NULL";
3941        $resArr = $this->db->getResultArray($queryStr);
3942
3943        return $resArr;
3944    } /* }}} */
3945
3946    /**
3947     * Return all documents revisors waiting for a revision to start (sleeping)
3948     * or are required to revise the document (waiting)
3949     *
3950     * This function retrieves all revisors which are waiting for
3951     * revision or already in revision
3952     * Note: the name of the method is somewhat misleading, because it
3953     * does not return documents but just database records from table
3954     * tblDocumentRevisors and tblDocumentRevisionLog
3955     *
3956     * @return array list of revisors or false in case of an error
3957     */
3958    function getDocumentsInRevision() { /* {{{ */
3959        if (!$this->db->createTemporaryTable("ttrevisionid") || !$this->db->createTemporaryTable("ttcontentid")) {
3960            return false;
3961        }
3962        $queryStr =
3963            "SELECT `tblDocumentRevisors`.*, `tblDocumentRevisionLog`.`status` FROM `tblDocumentRevisors` LEFT JOIN `ttrevisionid` ON `tblDocumentRevisors`.`revisionID` = `ttrevisionid`.`revisionID` LEFT JOIN `tblDocumentRevisionLog` ON `ttrevisionid`.`maxLogID` = `tblDocumentRevisionLog`.`revisionLogID` LEFT JOIN `ttcontentid` ON `ttcontentid`.`maxVersion`=`tblDocumentRevisors`.`version` AND `ttcontentid`.`document`=`tblDocumentRevisors`.`documentID` WHERE `tblDocumentRevisionLog`.`status` in (".S_LOG_WAITING.", ".S_LOG_SLEEPING.") AND `ttcontentid`.`maxVersion` IS NOT NULL";
3964        $resArr = $this->db->getResultArray($queryStr);
3965
3966        return $resArr;
3967    } /* }}} */
3968
3969    /**
3970     * Returns document content which is not linked to a document
3971     *
3972     * This method is for finding straying document content without
3973     * a parent document. In normal operation this should not happen
3974     * but little checks for database consistency and possible errors
3975     * in the application may have left over document content though
3976     * the document is gone already.
3977     *
3978     * @return array|bool
3979     */
3980    function getUnlinkedDocumentContent() { /* {{{ */
3981        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` NOT IN (SELECT id FROM `tblDocuments`)";
3982        $resArr = $this->db->getResultArray($queryStr);
3983        if ($resArr === false)
3984            return false;
3985
3986        $versions = array();
3987        foreach($resArr as $row) {
3988            /** @var SeedDMS_Core_Document $document */
3989            $document = new $this->classnames['document']($row['document'], '', '', '', '', '', '', '', '', '', '', '');
3990            $document->setDMS($this);
3991            $version = new $this->classnames['documentcontent']($row['id'], $document, $row['version'], $row['comment'], $row['date'], $row['createdBy'], $row['dir'], $row['orgFileName'], $row['fileType'], $row['mimeType'], $row['fileSize'], $row['checksum']);
3992            $versions[] = $version;
3993        }
3994        return $versions;
3995
3996    } /* }}} */
3997
3998    /**
3999     * Returns document content which has no file size set
4000     *
4001     * This method is for finding document content without a file size
4002     * set in the database. The file size of a document content was introduced
4003     * in version 4.0.0 of SeedDMS for implementation of user quotas.
4004     *
4005     * @return SeedDMS_Core_Document[]|bool
4006     */
4007    function getNoFileSizeDocumentContent() { /* {{{ */
4008        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `fileSize` = 0 OR `fileSize` is null";
4009        $resArr = $this->db->getResultArray($queryStr);
4010        if ($resArr === false)
4011            return false;
4012
4013        /** @var SeedDMS_Core_Document[] $versions */
4014        $versions = array();
4015        foreach($resArr as $row) {
4016            $document = $this->getDocument($row['document']);
4017            /* getting the document can fail if it is outside the root folder
4018             * and checkWithinRootDir is enabled.
4019             */
4020            if($document) {
4021                $version = new $this->classnames['documentcontent']($row['id'], $document, $row['version'], $row['comment'], $row['date'], $row['createdBy'], $row['dir'], $row['orgFileName'], $row['fileType'], $row['mimeType'], $row['fileSize'], $row['checksum'], $row['fileSize'], $row['checksum']);
4022                $versions[] = $version;
4023            }
4024        }
4025        return $versions;
4026
4027    } /* }}} */
4028
4029    /**
4030     * Returns document content which has no checksum set
4031     *
4032     * This method is for finding document content without a checksum
4033     * set in the database. The checksum of a document content was introduced
4034     * in version 4.0.0 of SeedDMS for finding duplicates.
4035     * @return bool|SeedDMS_Core_Document[]
4036     */
4037    function getNoChecksumDocumentContent() { /* {{{ */
4038        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `checksum` = '' OR `checksum` is null";
4039        $resArr = $this->db->getResultArray($queryStr);
4040        if ($resArr === false)
4041            return false;
4042
4043        /** @var SeedDMS_Core_Document[] $versions */
4044        $versions = array();
4045        foreach($resArr as $row) {
4046            $document = $this->getDocument($row['document']);
4047            /* getting the document can fail if it is outside the root folder
4048             * and checkWithinRootDir is enabled.
4049             */
4050            if($document) {
4051                $version = new $this->classnames['documentcontent']($row['id'], $document, $row['version'], $row['comment'], $row['date'], $row['createdBy'], $row['dir'], $row['orgFileName'], $row['fileType'], $row['mimeType'], $row['fileSize'], $row['checksum']);
4052                $versions[] = $version;
4053            }
4054        }
4055        return $versions;
4056
4057    } /* }}} */
4058
4059    /**
4060     * Returns document content which has the incorrect file type
4061     *
4062     * This method is for finding document content with an incorrect
4063     * or missing file type. It just checks documents contents
4064     * with a certain mime type.
4065     * @return bool|SeedDMS_Core_Document[]
4066     */
4067    function getWrongFiletypeDocumentContent() { /* {{{ */
4068        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `mimeType` in ('application/zip', 'application/pdf', 'image/png', 'image/gif', 'image/jpg', 'audio/mp3', 'text/rtf')";
4069        $resArr = $this->db->getResultArray($queryStr);
4070        if ($resArr === false)
4071            return false;
4072
4073        /** @var SeedDMS_Core_Document[] $versions */
4074        $versions = array();
4075        foreach($resArr as $row) {
4076            $expect = '';
4077            switch($row['mimeType']) {
4078            case "application/zip":
4079            case "application/pdf":
4080            case "image/png":
4081            case "image/gif":
4082            case "image/jpg":
4083            case "audio/mp3":
4084            case "text/rtf":
4085                $expect = substr($row['mimeType'], -3, 3);
4086                break;
4087            }
4088            if($expect) {
4089                if($row['fileType'] != '.'.$expect) {
4090                    /** @var SeedDMS_Core_Document $document */
4091                    $document = new $this->classnames['document']($row['document'], '', '', '', '', '', '', '', '', '', '', '');
4092                    $document->setDMS($this);
4093                    $version = new $this->classnames['documentcontent']($row['id'], $document, $row['version'], $row['comment'], $row['date'], $row['createdBy'], $row['dir'], $row['orgFileName'], $row['fileType'], $row['mimeType'], $row['fileSize'], $row['checksum']);
4094                    $versions[] = $version;
4095                }
4096            }
4097        }
4098        return $versions;
4099
4100    } /* }}} */
4101
4102    /**
4103     * Returns document content which is duplicated
4104     *
4105     * This method is for finding document content which is available twice
4106     * in the database. The checksum of a document content was introduced
4107     * in version 4.0.0 of SeedDMS for finding duplicates.
4108     * @return array|bool
4109     */
4110    function getDuplicateDocumentContent() { /* {{{ */
4111        $queryStr = "SELECT a.*, b.`id` as dupid FROM `tblDocumentContent` a LEFT JOIN `tblDocumentContent` b ON a.`checksum`=b.`checksum` WHERE a.`id`!=b.`id` ORDER BY a.`id` LIMIT 1000";
4112        $resArr = $this->db->getResultArray($queryStr);
4113        if ($resArr === false)
4114            return false;
4115
4116        /** @var SeedDMS_Core_Document[] $versions */
4117        $versions = array();
4118        foreach($resArr as $row) {
4119            $document = $this->getDocument($row['document']);
4120            /* getting the document can fail if it is outside the root folder
4121             * and checkWithinRootDir is enabled.
4122             */
4123            if($document) {
4124                $version = new $this->classnames['documentcontent']($row['id'], $document, $row['version'], $row['comment'], $row['date'], $row['createdBy'], $row['dir'], $row['orgFileName'], $row['fileType'], $row['mimeType'], $row['fileSize'], $row['checksum']);
4125                if(!isset($versions[$row['dupid']])) {
4126                    $versions[$row['id']]['content'] = $version;
4127                    $versions[$row['id']]['duplicates'] = array();
4128                } else
4129                    $versions[$row['dupid']]['duplicates'][] = $version;
4130            }
4131        }
4132        return $versions;
4133
4134    } /* }}} */
4135
4136    /**
4137     * Returns folders which contain documents with none unique sequence number
4138     *
4139     * This method is for finding folders with documents not having a
4140     * unique sequence number. Those documents cannot propperly be sorted
4141     * by sequence and changing their position is impossible if more than
4142     * two documents with the same sequence number exists, e.g.
4143     * doc 1: 3
4144     * doc 2: 5
4145     * doc 3: 5
4146     * doc 4: 5
4147     * doc 5: 7
4148     * If document 4 was to be moved between doc 1 and 2 it get sequence
4149     * number 4 ((5+3)/2).
4150     * But if document 4 was to be moved between doc 2 and 3 it will again
4151     * have sequence number 5.
4152     *
4153     * @return array|bool
4154     */
4155    function getDuplicateSequenceNo() { /* {{{ */
4156        $queryStr = "SELECT DISTINCT `folder` FROM (SELECT `folder`, `sequence`, count(*) c FROM `tblDocuments` GROUP BY `folder`, `sequence` HAVING c > 1) a";
4157        $resArr = $this->db->getResultArray($queryStr);
4158        if ($resArr === false)
4159            return false;
4160
4161        $folders = array();
4162        foreach($resArr as $row) {
4163            $folder = $this->getFolder($row['folder']);
4164            if($folder)
4165                $folders[] = $folder;
4166        }
4167        return $folders;
4168
4169    } /* }}} */
4170
4171    /**
4172     * Returns a list of reviews, approvals, receipts, revisions which are not
4173     * linked to a user, group anymore
4174     *
4175     * This method is for finding reviews or approvals whose user
4176     * or group  was deleted and not just removed from the process.
4177     *
4178     * @param string $process
4179     * @param string $usergroup
4180     * @return array
4181     */
4182    function getProcessWithoutUserGroup($process, $usergroup) { /* {{{ */
4183        switch($process) {
4184        case 'review':
4185            $queryStr = "SELECT a.*, b.`name` FROM `tblDocumentReviewers`";
4186            break;
4187        case 'approval':
4188            $queryStr = "SELECT a.*, b.`name` FROM `tblDocumentApprovers`";
4189            break;
4190        case 'receipt':
4191            $queryStr = "SELECT a.*, b.`name` FROM `tblDocumentRecipients`";
4192            break;
4193        case 'revision':
4194            $queryStr = "SELECT a.*, b.`name` FROM `tblDocumentRevisors`";
4195            break;
4196        }
4197        /** @noinspection PhpUndefinedVariableInspection */
4198        $queryStr .= " a LEFT JOIN `tblDocuments` b ON a.`documentID`=b.`id` WHERE";
4199        switch($usergroup) {
4200        case 'user':
4201            $queryStr .= " a.`type`=0 and a.`required` not in (SELECT `id` FROM `tblUsers`) ORDER BY b.`id`";
4202            break;
4203        case 'group':
4204            $queryStr .= " a.`type`=1 and a.`required` not in (SELECT `id` FROM `tblGroups`) ORDER BY b.`id`";
4205            break;
4206        }
4207        return $this->db->getResultArray($queryStr);
4208    } /* }}} */
4209
4210    /**
4211     * Removes all reviews, approvals, receipts, revisions which are not linked
4212     * to a user, group anymore
4213     *
4214     * This method is for removing all reviews or approvals whose user
4215     * or group  was deleted and not just removed from the process.
4216     * If the optional parameter $id is set, only this user/group id is removed.
4217     * @param string $process
4218     * @param string $usergroup
4219     * @param int $id
4220     * @return array
4221     */
4222    function removeProcessWithoutUserGroup($process, $usergroup, $id=0) { /* {{{ */
4223        /* Entries of tblDocumentReviewLog or tblDocumentApproveLog are deleted
4224         * because of CASCADE ON
4225         */
4226        switch($process) {
4227        case 'review':
4228            $queryStr = "DELETE FROM tblDocumentReviewers";
4229            break;
4230        case 'approval':
4231            $queryStr = "DELETE FROM tblDocumentApprovers";
4232            break;
4233        case 'receipt':
4234            $queryStr = "DELETE FROM tblDocumentRecipients";
4235            break;
4236        case 'revision':
4237            $queryStr = "DELETE FROM tblDocumentRevisors";
4238            break;
4239        }
4240        /** @noinspection PhpUndefinedVariableInspection */
4241        $queryStr .= " WHERE";
4242        switch($usergroup) {
4243        case 'user':
4244            $queryStr .= " type=0 AND";
4245            if($id)
4246                $queryStr .= " required=".((int) $id)." AND";
4247            $queryStr .= " required NOT IN (SELECT id FROM tblUsers)";
4248            break;
4249        case 'group':
4250            $queryStr .= " type=1 AND";
4251            if($id)
4252                $queryStr .= " required=".((int) $id)." AND";
4253            $queryStr .= " required NOT IN (SELECT id FROM tblGroups)";
4254            break;
4255        }
4256        return $this->db->getResultArray($queryStr);
4257    } /* }}} */
4258
4259    /**
4260     * Returns statitical information
4261     *
4262     * This method returns all kind of statistical information like
4263     * documents or used space per user, recent activity, etc.
4264     *
4265     * @param string $type type of statistic
4266     * @return array|bool returns false if the sql statement fails, returns an empty
4267     * array if no documents or folder where found, otherwise returns a non empty
4268     * array with statistical data
4269     */
4270    function getStatisticalData($type='') { /* {{{ */
4271        switch($type) {
4272            case 'docsperuser':
4273                $queryStr = "SELECT ".$this->db->concat(array('b.`fullName`', "' ('", 'b.`login`', "')'"))." AS `key`, count(`owner`) AS total FROM `tblDocuments` a LEFT JOIN `tblUsers` b ON a.`owner`=b.`id` GROUP BY `owner`, b.`fullName`";
4274                $resArr = $this->db->getResultArray($queryStr);
4275                if(is_bool($resArr) && $resArr == false)
4276                    return false;
4277
4278                return $resArr;
4279            case 'foldersperuser':
4280                $queryStr = "SELECT ".$this->db->concat(array('b.`fullName`', "' ('", 'b.`login`', "')'"))." AS `key`, count(`owner`) AS total FROM `tblFolders` a LEFT JOIN `tblUsers` b ON a.`owner`=b.`id` GROUP BY `owner`, b.`fullName`";
4281                $resArr = $this->db->getResultArray($queryStr);
4282                if(is_bool($resArr) && $resArr == false)
4283                    return false;
4284
4285                return $resArr;
4286            case 'docspermimetype':
4287                $queryStr = "SELECT b.`mimeType` AS `key`, count(`mimeType`) AS total FROM `tblDocuments` a LEFT JOIN `tblDocumentContent` b ON a.`id`=b.`document` GROUP BY b.`mimeType`";
4288                $resArr = $this->db->getResultArray($queryStr);
4289                if(is_bool($resArr) && $resArr == false)
4290                    return false;
4291
4292                return $resArr;
4293            case 'docspercategory':
4294                $queryStr = "SELECT b.`name` AS `key`, count(a.`categoryID`) AS total FROM `tblDocumentCategory` a LEFT JOIN `tblCategory` b ON a.`categoryID`=b.id GROUP BY a.`categoryID`, b.`name`";
4295                $resArr = $this->db->getResultArray($queryStr);
4296                if(is_bool($resArr) && $resArr == false)
4297                    return false;
4298
4299                return $resArr;
4300            case 'docsperstatus':
4301                /** @noinspection PhpUnusedLocalVariableInspection */
4302                $queryStr = "SELECT b.`status` AS `key`, count(b.`status`) AS total FROM (SELECT a.id, max(b.version), max(c.`statusLogID`) AS maxlog FROM `tblDocuments` a LEFT JOIN `tblDocumentStatus` b ON a.id=b.`documentID` LEFT JOIN `tblDocumentStatusLog` c ON b.`statusID`=c.`statusID` GROUP BY a.`id`, b.`version` ORDER BY a.`id`, b.`statusID`) a LEFT JOIN `tblDocumentStatusLog` b ON a.`maxlog`=b.`statusLogID` GROUP BY b.`status`";
4303                $queryStr = "SELECT b.`status` AS `key`, count(b.`status`) AS total FROM (SELECT a.`id`, max(c.`statusLogID`) AS maxlog FROM `tblDocuments` a LEFT JOIN `tblDocumentStatus` b ON a.id=b.`documentID` LEFT JOIN `tblDocumentStatusLog` c ON b.`statusID`=c.`statusID` GROUP BY a.`id` ORDER BY a.id) a LEFT JOIN `tblDocumentStatusLog` b ON a.maxlog=b.`statusLogID` GROUP BY b.`status`";
4304                $resArr = $this->db->getResultArray($queryStr);
4305                if(is_bool($resArr) && $resArr == false)
4306                    return false;
4307
4308                return $resArr;
4309            case 'docspermonth':
4310                $queryStr = "SELECT *, count(`key`) AS total FROM (SELECT ".$this->db->getDateExtract("date", '%Y-%m')." AS `key` FROM `tblDocuments`) a GROUP BY `key` ORDER BY `key`";
4311                $resArr = $this->db->getResultArray($queryStr);
4312                if(is_bool($resArr) && $resArr == false)
4313                    return false;
4314
4315                return $resArr;
4316            case 'docsaccumulated':
4317                $queryStr = "SELECT *, count(`key`) AS total FROM (SELECT ".$this->db->getDateExtract("date")." AS `key` FROM `tblDocuments`) a GROUP BY `key` ORDER BY `key`";
4318                $resArr = $this->db->getResultArray($queryStr);
4319                if(is_bool($resArr) && $resArr == false)
4320                    return false;
4321
4322                $sum = 0;
4323                foreach($resArr as &$res) {
4324                    $sum += $res['total'];
4325                    /* auxially variable $key is need because sqlite returns
4326                     * a key '`key`'
4327                     */
4328                    $res['key'] = mktime(12, 0, 0, substr($res['key'], 5, 2), substr($res['key'], 8, 2), substr($res['key'], 0, 4)) * 1000;
4329                    $res['total'] = $sum;
4330                }
4331                return $resArr;
4332            case 'docstotal':
4333                $queryStr = "SELECT count(*) AS total FROM `tblDocuments`";
4334                $resArr = $this->db->getResultArray($queryStr);
4335                if(is_bool($resArr) && $resArr == false)
4336                    return false;
4337                return (int) $resArr[0]['total'];
4338            case 'folderstotal':
4339                $queryStr = "SELECT count(*) AS total FROM `tblFolders`";
4340                $resArr = $this->db->getResultArray($queryStr);
4341                if(is_bool($resArr) && $resArr == false)
4342                    return false;
4343                return (int) $resArr[0]['total'];
4344            case 'userstotal':
4345                $queryStr = "SELECT count(*) AS total FROM `tblUsers`";
4346                $resArr = $this->db->getResultArray($queryStr);
4347                if(is_bool($resArr) && $resArr == false)
4348                    return false;
4349                return (int) $resArr[0]['total'];
4350            case 'sizeperuser':
4351                $queryStr = "SELECT ".$this->db->concat(array('c.`fullName`', "' ('", 'c.`login`', "')'"))." AS `key`, sum(`fileSize`) AS total FROM `tblDocuments` a LEFT JOIN `tblDocumentContent` b ON a.id=b.`document` LEFT JOIN `tblUsers` c ON a.`owner`=c.`id` GROUP BY a.`owner`, c.`fullName`";
4352                $resArr = $this->db->getResultArray($queryStr);
4353                if(is_bool($resArr) && $resArr == false)
4354                    return false;
4355
4356                return $resArr;
4357            default:
4358                return array();
4359        }
4360    } /* }}} */
4361
4362    /**
4363     * Returns changes with a period of time
4364     *
4365     * This method returns a list of all changes happened in the database
4366     * within a given period of time. It currently just checks for
4367     * entries in the database tables tblDocumentContent, tblDocumentFiles,
4368     * and tblDocumentStatusLog
4369     *
4370     * @param string $startts
4371     * @param string $endts
4372     * @return array|bool
4373     * @internal param string $start start date, defaults to start of current day
4374     * @internal param string $end end date, defaults to end of start day
4375     */
4376    function getTimeline($startts='', $endts='') { /* {{{ */
4377        if(!$startts)
4378            $startts = mktime(0, 0, 0);
4379        if(!$endts)
4380            $endts = $startts+86400;
4381
4382        /** @var SeedDMS_Core_Document[] $timeline */
4383        $timeline = array();
4384
4385        if(0) {
4386        $queryStr = "SELECT DISTINCT `document` FROM `tblDocumentContent` WHERE `date` > ".$startts." AND `date` < ".$endts." OR `revisiondate` > '".date('Y-m-d H:i:s', $startts)."' AND `revisiondate` < '".date('Y-m-d H:i:s', $endts)."' UNION SELECT DISTINCT `document` FROM `tblDocumentFiles` WHERE `date` > ".$startts." AND `date` < ".$endts;
4387        } else {
4388        $startdate = date('Y-m-d H:i:s', $startts);
4389        $enddate = date('Y-m-d H:i:s', $endts);
4390        $queryStr = "SELECT DISTINCT `documentID` AS `document` FROM `tblDocumentStatus` LEFT JOIN `tblDocumentStatusLog` ON `tblDocumentStatus`.`statusID`=`tblDocumentStatusLog`.`statusID` WHERE `date` > ".$this->db->qstr($startdate)." AND `date` < ".$this->db->qstr($enddate)." UNION SELECT DISTINCT document FROM `tblDocumentFiles` WHERE `date` > ".$this->db->qstr($startdate)." AND `date` < ".$this->db->qstr($enddate)." UNION SELECT DISTINCT `document` FROM `tblDocumentFiles` WHERE `date` > ".$startts." AND `date` < ".$endts;
4391        }
4392        $resArr = $this->db->getResultArray($queryStr);
4393        if ($resArr === false)
4394            return false;
4395        foreach($resArr as $rec) {
4396            $document = $this->getDocument($rec['document']);
4397            $timeline = array_merge($timeline, $document->getTimeline());
4398        }
4399        return $timeline;
4400
4401    } /* }}} */
4402
4403    /**
4404     * Returns changes with a period of time
4405     *
4406     * This method is similar to getTimeline() but returns more dedicated lists
4407     * of documents or folders which has change in various ways.
4408     *
4409     * @param string $mode
4410     * @param string $startts
4411     * @param string $endts
4412     * @return array|bool
4413     * @internal param string $start start date, defaults to start of current day
4414     * @internal param string $end end date, defaults to end of start day
4415     */
4416    function getLatestChanges($mode, $startts='', $endts='') { /* {{{ */
4417        if(!$startts)
4418            $startts = mktime(0, 0, 0);
4419        if(!$endts)
4420            $endts = $startts+86400;
4421
4422        $startdate = date('Y-m-d H:i:s', $startts);
4423        $enddate = date('Y-m-d H:i:s', $endts);
4424
4425        $objects = [];
4426        switch($mode) {
4427        case 'statuschange':
4428            /* Count entries in tblDocumentStatusLog for each tblDocumentStatus and
4429             * take only those into account with at least 2 log entries. For the
4430             * document id do a left join with tblDocumentStatus
4431             * This is similar to ttstatid + the count + the join
4432             * c > 1 is required to find only those documents with a changed status
4433             * This sql statement appears to be much to complicated.
4434             */
4435            //$queryStr = "SELECT `a`.*, `tblDocumentStatus`.`documentID` as `document` FROM (SELECT `tblDocumentStatusLog`.`statusID` AS `statusID`, MAX(`tblDocumentStatusLog`.`statusLogID`) AS `maxLogID`, COUNT(`tblDocumentStatusLog`.`statusLogID`) AS `c`, `tblDocumentStatusLog`.`date` FROM `tblDocumentStatusLog` GROUP BY `tblDocumentStatusLog`.`statusID` HAVING `c` > 1 ORDER BY `tblDocumentStatusLog`.`date` DESC) `a` LEFT JOIN `tblDocumentStatus` ON `a`.`statusID`=`tblDocumentStatus`.`statusID` WHERE `a`.`date` > ".$this->db->qstr($startdate)." AND `a`.`date` < ".$this->db->qstr($enddate)." ";
4436            $queryStr = "SELECT DISTINCT `tblDocumentStatus`.`documentID` as    `document` FROM `tblDocumentStatusLog` LEFT JOIN `tblDocumentStatus` ON `tblDocumentStatusLog`.`statusID` = `tblDocumentStatus`.`statusID` WHERE `tblDocumentStatusLog`.`date` > ".$this->db->qstr($startdate)." AND `tblDocumentStatusLog`.`date` < ".$this->db->qstr($enddate)." ORDER BY `tblDocumentStatusLog`.`date` DESC";
4437            $resArr = $this->db->getResultArray($queryStr);
4438            if ($resArr === false)
4439                return false;
4440            foreach($resArr as $rec) {
4441                if($object = $this->getDocument($rec['document']))
4442                    $objects[] = $object;
4443            }
4444            break;
4445        case 'newdocuments':
4446            $queryStr = "SELECT `id` AS `document` FROM `tblDocuments` WHERE `date` > ".$startts." AND `date` < ".$endts." ORDER BY `date` DESC";
4447            $resArr = $this->db->getResultArray($queryStr);
4448            if ($resArr === false)
4449                return false;
4450            foreach($resArr as $rec) {
4451                if($object = $this->getDocument($rec['document']))
4452                    $objects[] = $object;
4453            }
4454            break;
4455        case 'updateddocuments':
4456            /* DISTINCT is need if there is more than 1 update of the document in the
4457             * given period of time. Without it, the query will return the document
4458             * more than once.
4459             */
4460            $queryStr = "SELECT DISTINCT `document` AS `document` FROM `tblDocumentContent` LEFT JOIN `tblDocuments` ON `tblDocumentContent`.`document`=`tblDocuments`.`id` WHERE `tblDocumentContent`.`date` > ".$startts." AND `tblDocumentContent`.`date` < ".$endts." AND `tblDocumentContent`.`date` > `tblDocuments`.`date` ORDER BY `tblDocumentContent`.`date` DESC";
4461            $resArr = $this->db->getResultArray($queryStr);
4462            if ($resArr === false)
4463                return false;
4464            foreach($resArr as $rec) {
4465                if($object = $this->getDocument($rec['document']))
4466                    $objects[] = $object;
4467            }
4468            break;
4469        case 'newfolders':
4470            $queryStr = "SELECT `id` AS `folder` FROM `tblFolders` WHERE `date` > ".$startts." AND `date` < ".$endts." ORDER BY `date` DESC";
4471            $resArr = $this->db->getResultArray($queryStr);
4472            if ($resArr === false)
4473                return false;
4474            foreach($resArr as $rec) {
4475                if($object = $this->getFolder($rec['folder']))
4476                    $objects[] = $object;
4477            }
4478            break;
4479        }
4480        return $objects;
4481    } /* }}} */
4482
4483    /**
4484     * Set a callback function
4485     *
4486     * The function passed in $func must be a callable and $name may not be empty.
4487     *
4488     * Setting a callback with the method will remove all priorly set callbacks.
4489     *
4490     * @param string $name internal name of callback
4491     * @param mixed $func function name as expected by {call_user_method}
4492     * @param mixed $params parameter passed as the first argument to the
4493     *        callback
4494     * @return bool true if adding the callback succeeds otherwise false
4495     */
4496    function setCallback($name, $func, $params=null) { /* {{{ */
4497        if($name && $func && is_callable($func)) {
4498            $this->callbacks[$name] = array(array($func, $params));
4499            return true;
4500        } else {
4501            return false;
4502        }
4503    } /* }}} */
4504
4505    /**
4506     * Add a callback function
4507     *
4508     * The function passed in $func must be a callable and $name may not be empty.
4509     *
4510     * @param string $name internal name of callback
4511     * @param mixed $func function name as expected by {call_user_method}
4512     * @param mixed $params parameter passed as the first argument to the
4513     *        callback
4514     * @return bool true if adding the callback succeeds otherwise false
4515     */
4516    function addCallback($name, $func, $params=null) { /* {{{ */
4517        if($name && $func && is_callable($func)) {
4518            $this->callbacks[$name][] = array($func, $params);
4519            return true;
4520        } else {
4521            return false;
4522        }
4523    } /* }}} */
4524
4525    /**
4526     * Check if a callback with the given has been set
4527     *
4528     * @param string $name internal name of callback
4529     * @return bool true a callback exists otherwise false
4530     */
4531    function hasCallback($name) { /* {{{ */
4532        if($name && !empty($this->callbacks[$name]))
4533            return true;
4534        return false;
4535    } /* }}} */
4536
4537}