Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
64.64% covered (warning)
64.64%
2190 / 3388
38.29% covered (danger)
38.29%
85 / 222
CRAP
0.00% covered (danger)
0.00%
0 / 5
SeedDMS_Core_Document
69.10% covered (warning)
69.10%
975 / 1411
40.23% covered (danger)
40.23%
35 / 87
11662.48
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
4
 clearCache
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 isType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSearchFields
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
6
 getInstanceByData
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getInstance
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 applyDecorators
33.33% covered (danger)
33.33%
2 / 6
0.00% covered (danger)
0.00%
0 / 1
5.67
 getDir
50.00% covered (danger)
50.00%
2 / 4
0.00% covered (danger)
0.00%
0 / 1
2.50
 getName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setName
52.94% covered (warning)
52.94%
9 / 17
0.00% covered (danger)
0.00%
0 / 1
14.67
 getComment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setComment
52.94% covered (warning)
52.94%
9 / 17
0.00% covered (danger)
0.00%
0 / 1
14.67
 getKeywords
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setKeywords
52.94% covered (warning)
52.94%
9 / 17
0.00% covered (danger)
0.00%
0 / 1
14.67
 hasCategory
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 getCategories
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 setCategories
68.00% covered (warning)
68.00%
17 / 25
0.00% covered (danger)
0.00%
0 / 1
13.28
 addCategories
72.41% covered (warning)
72.41%
21 / 29
0.00% covered (danger)
0.00%
0 / 1
15.02
 removeCategories
60.00% covered (warning)
60.00%
12 / 20
0.00% covered (danger)
0.00%
0 / 1
14.18
 getDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDate
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 isDescendant
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getParent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFolder
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setParent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setFolder
71.43% covered (warning)
71.43%
25 / 35
0.00% covered (danger)
0.00%
0 / 1
16.94
 getOwner
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setOwner
69.23% covered (warning)
69.23%
18 / 26
0.00% covered (danger)
0.00%
0 / 1
12.91
 getDefaultAccess
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 setDefaultAccess
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 inheritsAccess
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getInheritAccess
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setInheritAccess
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 expires
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getExpires
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setExpires
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 hasExpired
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 verifyLastestContentExpriry
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
11.10
 checkForDueRevisionWorkflow
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 isLocked
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setLocked
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
 getLockingUser
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 isCheckedOut
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 getCheckOutInfo
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 checkOut
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
42
 checkIn
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
90
 cancelCheckOut
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 checkOutStatus
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
42
 getSequence
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setSequence
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 clearAccessList
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
 getAccessList
95.83% covered (success)
95.83%
23 / 24
0.00% covered (danger)
0.00%
0 / 1
12
 addAccess
88.89% covered (warning)
88.89%
16 / 18
0.00% covered (danger)
0.00%
0 / 1
8.09
 changeAccess
87.50% covered (warning)
87.50%
14 / 16
0.00% covered (danger)
0.00%
0 / 1
5.05
 removeAccess
81.25% covered (warning)
81.25%
13 / 16
0.00% covered (danger)
0.00%
0 / 1
6.24
 getAccessMode
83.33% covered (warning)
83.33%
25 / 30
0.00% covered (danger)
0.00%
0 / 1
23.04
 getGroupAccessMode
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
7.12
 getNotifyList
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
10.02
 cleanNotifyList
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 addNotify
55.00% covered (warning)
55.00%
22 / 40
0.00% covered (danger)
0.00%
0 / 1
43.34
 removeNotify
80.00% covered (warning)
80.00%
16 / 20
0.00% covered (danger)
0.00%
0 / 1
8.51
 addContent
51.58% covered (warning)
51.58%
49 / 95
0.00% covered (danger)
0.00%
0 / 1
201.93
 replaceContent
76.60% covered (warning)
76.60%
36 / 47
0.00% covered (danger)
0.00%
0 / 1
19.28
 getContent
87.50% covered (warning)
87.50%
14 / 16
0.00% covered (danger)
0.00%
0 / 1
7.10
 getContentByVersion
71.43% covered (warning)
71.43%
15 / 21
0.00% covered (danger)
0.00%
0 / 1
13.82
 isLatestContent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __getLatestContent
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 getLatestContent
87.50% covered (warning)
87.50%
14 / 16
0.00% covered (danger)
0.00%
0 / 1
8.12
 _removeContent
62.50% covered (warning)
62.50%
75 / 120
0.00% covered (danger)
0.00%
0 / 1
140.51
 removeContent
62.96% covered (warning)
62.96%
17 / 27
0.00% covered (danger)
0.00%
0 / 1
23.96
 getDocumentLink
82.35% covered (warning)
82.35%
14 / 17
0.00% covered (danger)
0.00%
0 / 1
8.35
 getDocumentLinks
95.45% covered (success)
95.45%
21 / 22
0.00% covered (danger)
0.00%
0 / 1
10
 getReverseDocumentLinks
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
8.01
 addDocumentLink
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
10.01
 removeDocumentLink
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
 getDocumentFile
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
6.02
 getDocumentFiles
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
9
 addDocumentFile
69.57% covered (warning)
69.57%
16 / 23
0.00% covered (danger)
0.00%
0 / 1
9.80
 removeDocumentFile
76.47% covered (warning)
76.47%
13 / 17
0.00% covered (danger)
0.00%
0 / 1
8.83
 remove
59.70% covered (warning)
59.70%
40 / 67
0.00% covered (danger)
0.00%
0 / 1
70.24
 __getApproversList
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getReadAccessList
68.42% covered (warning)
68.42%
39 / 57
0.00% covered (danger)
0.00%
0 / 1
47.29
 getFolderList
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 repair
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
 getUsedDiskSpace
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 getTimeline
87.88% covered (warning)
87.88%
29 / 33
0.00% covered (danger)
0.00%
0 / 1
13.30
 transferToUser
62.50% covered (warning)
62.50%
15 / 24
0.00% covered (danger)
0.00%
0 / 1
7.90
SeedDMS_Core_DocumentContent
60.80% covered (warning)
60.80%
1101 / 1811
20.83% covered (danger)
20.83%
20 / 96
39463.04
0.00% covered (danger)
0.00%
0 / 1
 verifyStatus
93.22% covered (success)
93.22%
55 / 59
0.00% covered (danger)
0.00%
0 / 1
38.45
 __construct
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
2
 getInstance
83.33% covered (warning)
83.33%
15 / 18
0.00% covered (danger)
0.00%
0 / 1
6.17
 isType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getComment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getOriginalFileName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFileType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFileName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __getDir
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMimeType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRevisionDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDocument
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUser
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getPath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setRevisionDate
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 setDate
84.62% covered (warning)
84.62%
11 / 13
0.00% covered (danger)
0.00%
0 / 1
6.13
 getFileSize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setFileSize
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
3.10
 getChecksum
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setChecksum
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
3.10
 setFileType
80.00% covered (warning)
80.00%
12 / 15
0.00% covered (danger)
0.00%
0 / 1
5.20
 setMimeType
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
4.32
 setComment
43.75% covered (danger)
43.75%
7 / 16
0.00% covered (danger)
0.00%
0 / 1
19.39
 getStatus
89.47% covered (warning)
89.47%
17 / 19
0.00% covered (danger)
0.00%
0 / 1
6.04
 getStatusLog
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
5.01
 setStatus
91.18% covered (success)
91.18%
31 / 34
0.00% covered (danger)
0.00%
0 / 1
16.18
 rewriteStatusLog
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
56
 getAccessMode
7.23% covered (danger)
7.23%
6 / 83
0.00% covered (danger)
0.00%
0 / 1
1589.76
 getReviewers
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
7.01
 getReviewStatus
87.88% covered (warning)
87.88%
29 / 33
0.00% covered (danger)
0.00%
0 / 1
12.26
 getReviewLog
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 rewriteReviewLog
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
240
 getApprovers
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
7.01
 getApprovalStatus
87.88% covered (warning)
87.88%
29 / 33
0.00% covered (danger)
0.00%
0 / 1
12.26
 getApproveLog
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 rewriteApprovalLog
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
240
 getRecipients
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
7.01
 getReceiptStatus
85.37% covered (warning)
85.37%
35 / 41
0.00% covered (danger)
0.00%
0 / 1
14.61
 getReceiptLog
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 rewriteReceiptLog
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
240
 getRevisors
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
7.01
 getRevisionStatus
90.00% covered (success)
90.00%
27 / 30
0.00% covered (danger)
0.00%
0 / 1
10.10
 getRevisionLog
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 rewriteRevisionLog
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
240
 checkForDueRevisionWorkflow
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
182
 addIndReviewer
84.38% covered (warning)
84.38%
27 / 32
0.00% covered (danger)
0.00%
0 / 1
18.10
 addGrpReviewer
86.11% covered (warning)
86.11%
31 / 36
0.00% covered (danger)
0.00%
0 / 1
21.07
 setReviewByInd
77.78% covered (warning)
77.78%
21 / 27
0.00% covered (danger)
0.00%
0 / 1
13.58
 removeReview
80.95% covered (warning)
80.95%
17 / 21
0.00% covered (danger)
0.00%
0 / 1
10.69
 setReviewByGrp
76.92% covered (warning)
76.92%
20 / 26
0.00% covered (danger)
0.00%
0 / 1
13.77
 addIndApprover
84.38% covered (warning)
84.38%
27 / 32
0.00% covered (danger)
0.00%
0 / 1
18.10
 addGrpApprover
86.11% covered (warning)
86.11%
31 / 36
0.00% covered (danger)
0.00%
0 / 1
21.07
 setApprovalByInd
77.78% covered (warning)
77.78%
21 / 27
0.00% covered (danger)
0.00%
0 / 1
13.58
 removeApproval
80.95% covered (warning)
80.95%
17 / 21
0.00% covered (danger)
0.00%
0 / 1
10.69
 setApprovalByGrp
76.92% covered (warning)
76.92%
20 / 26
0.00% covered (danger)
0.00%
0 / 1
13.77
 addIndRecipient
84.38% covered (warning)
84.38%
27 / 32
0.00% covered (danger)
0.00%
0 / 1
18.10
 addGrpRecipient
87.18% covered (warning)
87.18%
34 / 39
0.00% covered (danger)
0.00%
0 / 1
20.84
 addRevisor
82.50% covered (warning)
82.50%
33 / 40
0.00% covered (danger)
0.00%
0 / 1
20.93
 addIndRevisor
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 addGrpRevisor
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 setReceiptByInd
80.00% covered (warning)
80.00%
20 / 25
0.00% covered (danger)
0.00%
0 / 1
11.97
 setReceiptByGrp
80.00% covered (warning)
80.00%
20 / 25
0.00% covered (danger)
0.00%
0 / 1
11.97
 setRevision
80.00% covered (warning)
80.00%
24 / 30
0.00% covered (danger)
0.00%
0 / 1
16.80
 setRevisionByInd
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 setRevisionByGrp
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 delIndReviewer
70.59% covered (warning)
70.59%
12 / 17
0.00% covered (danger)
0.00%
0 / 1
11.06
 delGrpReviewer
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
72
 delIndApprover
72.22% covered (warning)
72.22%
13 / 18
0.00% covered (danger)
0.00%
0 / 1
9.37
 delGrpApprover
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
72
 delIndRecipient
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
56
 delGrpRecipient
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
56
 delRevisor
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
182
 delIndRevisor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 delGrpRevisor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 startRevision
80.00% covered (warning)
80.00%
20 / 25
0.00% covered (danger)
0.00%
0 / 1
8.51
 finishRevision
76.00% covered (warning)
76.00%
19 / 25
0.00% covered (danger)
0.00%
0 / 1
11.38
 setWorkflowState
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
3.14
 getWorkflowState
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
5.01
 setWorkflow
66.67% covered (warning)
66.67%
14 / 21
0.00% covered (danger)
0.00%
0 / 1
8.81
 getWorkflow
88.24% covered (warning)
88.24%
15 / 17
0.00% covered (danger)
0.00%
0 / 1
6.06
 rewriteWorkflowLog
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
72
 rewindWorkflow
76.92% covered (warning)
76.92%
10 / 13
0.00% covered (danger)
0.00%
0 / 1
3.11
 removeWorkflow
83.33% covered (warning)
83.33%
20 / 24
0.00% covered (danger)
0.00%
0 / 1
8.30
 getParentWorkflow
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
56
 runSubWorkflow
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 returnFromSubWorkflow
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
56
 triggerWorkflowTransitionIsAllowed
66.67% covered (warning)
66.67%
18 / 27
0.00% covered (danger)
0.00%
0 / 1
19.26
 executeWorkflowTransitionIsAllowed
52.78% covered (warning)
52.78%
19 / 36
0.00% covered (danger)
0.00%
0 / 1
42.96
 triggerWorkflowTransition
63.16% covered (warning)
63.16%
12 / 19
0.00% covered (danger)
0.00%
0 / 1
13.05
 enterNextState
80.00% covered (warning)
80.00%
20 / 25
0.00% covered (danger)
0.00%
0 / 1
11.97
 getWorkflowLog
86.36% covered (warning)
86.36%
19 / 22
0.00% covered (danger)
0.00%
0 / 1
8.16
 getLastWorkflowLog
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
3.01
 needsWorkflowAction
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 repair
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
132
SeedDMS_Core_DocumentLink
85.00% covered (warning)
85.00%
17 / 20
87.50% covered (warning)
87.50%
7 / 8
13.57
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 isType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getID
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDocument
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTarget
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUser
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 isPublic
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAccessMode
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
6.00
SeedDMS_Core_DocumentFile
94.37% covered (success)
94.37%
67 / 71
90.91% covered (success)
90.91%
20 / 22
38.26
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
 isType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getID
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDocument
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUserID
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getComment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setComment
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 getDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDate
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 getDir
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFileType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMimeType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getOriginalFileName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setName
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 getUser
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getPath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setVersion
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 isPublic
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setPublic
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 getAccessMode
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
6.00
SeedDMS_Core_AddContentResultSet
22.41% covered (danger)
22.41%
13 / 58
33.33% covered (danger)
33.33%
3 / 9
865.86
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 setDMS
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addReviewer
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
132
 addApprover
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
132
 setStatus
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
4.59
 getStatus
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getContent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getReviewers
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
42
 getApprovers
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2declare(strict_types=1);
3
4/**
5 * Implementation of a document in the document management system
6 *
7 * @category   DMS
8 * @package    SeedDMS_Core
9 * @license    GPL2
10 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
11 *             Uwe Steinmann <uwe@steinmann.cx>
12 * @copyright  Copyright (C) 2002-2005 Markus Westphal, 2006-2008 Malcolm Cowe,
13 *             2010 Matteo Lucarelli, 2010-2024 Uwe Steinmann
14 * @version    Release: @package_version@
15 */
16
17/**
18 * The different states a document can be in
19 */
20/*
21 * Document is in review state. A document is in review state when
22 * it needs to be reviewed by a user or group.
23 */
24define("S_DRAFT_REV", 0);
25
26/*
27 * Document is in approval state. A document is in approval state when
28 * it needs to be approved by a user or group.
29 */
30define("S_DRAFT_APP", 1);
31
32/*
33 * Document is released. A document is in release state either when
34 * it needs no review or approval after uploaded or has been reviewed
35 * and/or approved.
36 */
37define("S_RELEASED",  2);
38
39/*
40 * Document is in workflow. A document is in workflow if a workflow
41 * has been started and has not reached a final state.
42 */
43define("S_IN_WORKFLOW",  3);
44
45/*
46 * Document is in a revision workflow. A revision workflow is started
47 * some time after the document has been released.
48 */
49define("S_IN_REVISION",  4);
50
51/*
52 * Document is in draft status. Being in draft means that the document
53 * is still worked on. This status is mainly for uploading documents
54 * which aren't fully complete but needs to accessible for the public,
55 * e.g. in order to colaborate on them.
56 */
57define("S_DRAFT",  5);
58
59/*
60 * Document needs correction after revision. This needs to be different from
61 * the regular S_REJECTED because documents which has been rejected
62 * in revision are not necessarily invalid but just needs correction.
63 */
64define("S_NEEDS_CORRECTION",  6);
65
66/*
67 * Document was rejected. A document is in rejected state when
68 * the review failed or approval was not given.
69 */
70define("S_REJECTED", -1);
71
72/*
73 * Document is obsolete. A document can be obsoleted once it was
74 * released.
75 */
76define("S_OBSOLETE", -2);
77
78/*
79 * Document is expired. A document expires when the expiration date
80 * is reached
81 */
82define("S_EXPIRED",  -3);
83
84/*
85 * Lowest and highest status that may be set
86 */
87define("S_LOWEST_STATUS",  -3);
88define("S_HIGHEST_STATUS",  6);
89
90/**
91 * The different states a workflow log can be in. This is used in
92 * all tables tblDocumentXXXLog
93 */
94/*
95 * workflow is in a neutral status waiting for action of user
96 */
97define("S_LOG_WAITING",  0);
98
99/*
100 * workflow has been successful ended. The document content has been
101 * approved, reviewed, aknowledged or revised
102 */
103define("S_LOG_ACCEPTED",  1);
104
105/*
106 * workflow has been unsuccessful ended. The document content has been
107 * rejected
108 */
109define("S_LOG_REJECTED",  -1);
110
111/*
112 * user has been removed from workflow. This can be for different reasons
113 * 1. the user has been actively removed from the workflow, 2. the user has
114 * been deleted.
115 */
116define("S_LOG_USER_REMOVED",  -2);
117
118/*
119 * workflow is sleeping until reactivation. The workflow has been set up
120 * but not started. This is only valid for the revision workflow, which
121 * may run over and over again.
122 */
123define("S_LOG_SLEEPING",  -3);
124
125/**
126 * Class to represent a document in the document management system
127 *
128 * A document in SeedDMS is a collection of content elements which are
129 * similar to a file in a regular file system.
130 * Documents may have any number of content elements
131 * ({@see SeedDMS_Core_DocumentContent}). These content elements are often
132 * called versions ordered in a timely manner. The most recent content element
133 * is the current version of the document.
134 *
135 * Documents can be linked to other documents, can have attached files,
136 * can be assigned to a category and have additional attributes.
137 * The document content can be anything that can be stored in a regular
138 * file.
139 *
140 * @category   DMS
141 * @package    SeedDMS_Core
142 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
143 *             Uwe Steinmann <uwe@steinmann.cx>
144 * @copyright  Copyright (C) 2002-2005 Markus Westphal, 2006-2008 Malcolm Cowe,
145 *             2010 Matteo Lucarelli, 2010-2024 Uwe Steinmann
146 * @version    Release: @package_version@
147 */
148class SeedDMS_Core_Document extends SeedDMS_Core_Object { /* {{{ */
149    /**
150     * @var string name of document
151     */
152    protected $_name;
153
154    /**
155     * @var string comment of document
156     */
157    protected $_comment;
158
159    /**
160     * @var integer unix timestamp of creation date
161     */
162    protected $_date;
163
164    /**
165     * @var integer id of user who is the owner
166     */
167    protected $_ownerID;
168
169    /**
170     * @var object user who is the owner
171     */
172    protected $_owner;
173
174    /**
175     * @var integer id of folder this document belongs to
176     */
177    protected $_folderID;
178
179    /**
180     * @var object parent folder this document belongs to
181     */
182    protected $_parent;
183
184    /**
185     * @var integer timestamp of expiration date
186     */
187    protected $_expires;
188
189    /**
190     * @var boolean true if access is inherited, otherwise false
191     */
192    protected $_inheritAccess;
193
194    /**
195     * @var integer default access if access rights are not inherited
196     */
197    protected $_defaultAccess;
198
199    /**
200     * @var array list of notifications for users and groups
201     */
202    protected $_readAccessList;
203
204    /**
205     * @var array list of notifications for users and groups
206     */
207    public $_notifyList;
208
209    /**
210     * @var boolean true if document is locked, otherwise false
211     */
212    protected $_locked;
213
214    /**
215     * @var string list of keywords
216     */
217    protected $_keywords;
218
219    /**
220     * @var SeedDMS_Core_DocumentCategory[] list of categories
221     */
222    protected $_categories;
223
224    /**
225     * @var integer position of document within the parent folder
226     */
227    protected $_sequence;
228
229    /**
230     * @var SeedDMS_Core_DocumentContent temp. storage for latestcontent
231     */
232    protected $_latestContent;
233
234    /**
235     * @var array temp. storage for content
236     */
237    protected $_content;
238
239    /**
240     * @var SeedDMS_Core_Folder
241     */
242    protected $_folder;
243
244    /** @var array of SeedDMS_Core_UserAccess and SeedDMS_Core_GroupAccess */
245    protected $_accessList;
246
247    /**
248     * @var array
249     */
250    protected $_documentLinks;
251
252    /**
253     * @var array
254     */
255    protected $_documentFiles;
256
257    public function __construct($id, $name, $comment, $date, $expires, $ownerID, $folderID, $inheritAccess, $defaultAccess, $locked, $keywords, $sequence) { /* {{{ */
258        parent::__construct($id);
259        $this->_name = $name;
260        $this->_comment = $comment;
261        $this->_date = $date;
262        $this->_expires = $expires;
263        $this->_ownerID = $ownerID;
264        $this->_folderID = $folderID;
265        $this->_inheritAccess = $inheritAccess ? true : false;
266        $this->_defaultAccess = $defaultAccess;
267        $this->_locked = ($locked == null || $locked == '' ? -1 : $locked);
268        $this->_keywords = $keywords;
269        $this->_sequence = $sequence;
270        $this->_categories = array();
271        $this->_notifyList = array();
272        $this->_latestContent = null;
273        $this->_content = null;
274        /* Cache */
275        $this->clearCache();
276    } /* }}} */
277
278    /**
279     * Clear cache of this instance.
280     *
281     * The result of some expensive database actions (e.g. get all subfolders
282     * or documents) will be saved in a class variable to speed up consecutive
283     * calls of the same method. If a second call of the same method shall not
284     * use the cache, then it must be cleared.
285     *
286     */
287    public function clearCache() { /* {{{ */
288        $this->_parent = null;
289        $this->_owner = null;
290        $this->_documentLinks = null;
291        $this->_documentFiles = null;
292        $this->_content = null;
293        $this->_accessList = null;
294        $this->_notifyList = array();
295        $this->_readAccessList = array();
296    } /* }}} */
297
298    /**
299     * Check if this object is of type 'document'.
300     *
301     * @param string $type type of object
302     */
303    public function isType($type) { /* {{{ */
304        return $type == 'document';
305    } /* }}} */
306
307    /**
308     * Return an array of database fields which are used for searching
309     * a term entered in the database search form
310     *
311     * @param SeedDMS_Core_DMS $dms
312     * @param array $searchin integer list of search scopes (2=name, 3=comment,
313     * 4=attributes)
314     * @return array list of database fields
315     */
316    public static function getSearchFields($dms, $searchin) { /* {{{ */
317        $db = $dms->getDB();
318
319        $searchFields = array();
320        if (in_array(1, $searchin)) {
321            $searchFields[] = "`tblDocuments`.`keywords`";
322        }
323        if (in_array(2, $searchin)) {
324            $searchFields[] = "`tblDocuments`.`name`";
325        }
326        if (in_array(3, $searchin)) {
327            $searchFields[] = "`tblDocuments`.`comment`";
328            $searchFields[] = "`tblDocumentContent`.`comment`";
329        }
330        if (in_array(4, $searchin)) {
331            $searchFields[] = "`tblDocumentAttributes`.`value`";
332            $searchFields[] = "`tblDocumentContentAttributes`.`value`";
333        }
334        if (in_array(5, $searchin)) {
335            $searchFields[] = $db->castToText("`tblDocuments`.`id`");
336        }
337
338        return $searchFields;
339    } /* }}} */
340
341    /**
342     * Return a folder by its database record
343     *
344     * @param array $resArr array of folder data as returned by database
345     * @param SeedDMS_Core_DMS $dms
346     * @return SeedDMS_Core_Folder|bool instance of SeedDMS_Core_Folder if document exists
347     */
348    public static function getInstanceByData($resArr, $dms) { /* {{{ */
349        $classname = $dms->getClassname('document');
350        /** @var SeedDMS_Core_Document $document */
351        $document = new $classname($resArr["id"], $resArr["name"], $resArr["comment"], $resArr["date"], $resArr["expires"], $resArr["owner"], $resArr["folder"], $resArr["inheritAccess"], $resArr["defaultAccess"], $resArr['lock'], $resArr["keywords"], $resArr["sequence"]);
352        $document->setDMS($dms);
353        $document = $document->applyDecorators();
354        return $document;
355    } /* }}} */
356
357    /**
358     * Return an document by its id
359     *
360     * @param integer $id id of document
361     * @param SeedDMS_Core_DMS $dms
362     * @return bool|SeedDMS_Core_Document instance of SeedDMS_Core_Document if document exists, null
363     * if document does not exist, false in case of error
364     */
365    public static function getInstance($id, $dms) { /* {{{ */
366        $db = $dms->getDB();
367
368//        $queryStr = "SELECT * FROM `tblDocuments` WHERE `id` = " . (int) $id;
369        $queryStr = "SELECT `tblDocuments`.*, `tblDocumentLocks`.`userID` as `lock` FROM `tblDocuments` LEFT JOIN `tblDocumentLocks` ON `tblDocuments`.`id` = `tblDocumentLocks`.`document` WHERE `id` = " . (int) $id;
370        if($dms->checkWithinRootDir)
371            $queryStr .= " AND `folderList` LIKE '%:".$dms->rootFolderID.":%'";
372        $resArr = $db->getResultArray($queryStr);
373        if (is_bool($resArr) && $resArr == false)
374            return false;
375        if (count($resArr) != 1)
376            return null;
377        $resArr = $resArr[0];
378
379        $resArr['lock'] = !$resArr['lock'] ? -1 : $resArr['lock'];
380
381        return self::getInstanceByData($resArr, $dms);
382    } /* }}} */
383
384    /**
385     * Apply decorators
386     *
387     * @return object final object after all decorators has been applied
388     */
389    public function applyDecorators() { /* {{{ */
390        if($decorators = $this->_dms->getDecorators('document')) {
391            $s = $this;
392            foreach($decorators as $decorator) {
393                $s = new $decorator($s);
394            }
395            return $s;
396        } else {
397            return $this;
398        }
399    } /* }}} */
400
401    /**
402     * Return the directory of the document in the file system relativ
403     * to the contentDir
404     *
405     * @return string directory of document
406     */
407    public function getDir() { /* {{{ */
408        if($this->_dms->maxDirID) {
409            $dirid = (int) (($this->_id-1) / $this->_dms->maxDirID) + 1;
410            return $dirid.DIRECTORY_SEPARATOR.$this->_id.DIRECTORY_SEPARATOR;
411        } else {
412            return $this->_id.DIRECTORY_SEPARATOR;
413        }
414    } /* }}} */
415
416    /**
417     * Return the name of the document
418     *
419     * @return string name of document
420     */
421    public function getName() { return $this->_name; }
422
423    /**
424     * Set the name of the document
425     *
426     * @param $newName string new name of document
427     * @return bool
428     */
429    public function setName($newName) { /* {{{ */
430        $db = $this->_dms->getDB();
431
432        /* Check if 'onPreSetName' callback is set */
433        if(isset($this->_dms->callbacks['onPreSetName'])) {
434            foreach($this->_dms->callbacks['onPreSetName'] as $callback) {
435                $ret = call_user_func($callback[0], $callback[1], $this, $newName);
436                if(is_bool($ret))
437                    return $ret;
438            }
439        }
440
441        $queryStr = "UPDATE `tblDocuments` SET `name` = ".$db->qstr($newName)." WHERE `id` = ". $this->_id;
442        if (!$db->getResult($queryStr))
443            return false;
444
445        $oldName = $this->_name;
446        $this->_name = $newName;
447
448        /* Check if 'onPostSetName' callback is set */
449        if(isset($this->_dms->callbacks['onPostSetName'])) {
450            foreach($this->_dms->callbacks['onPostSetName'] as $callback) {
451                $ret = call_user_func($callback[0], $callback[1], $this, $oldName);
452                if(is_bool($ret))
453                    return $ret;
454            }
455        }
456
457        return true;
458    } /* }}} */
459
460    /**
461     * Return the comment of the document
462     *
463     * @return string comment of document
464     */
465    public function getComment() { return $this->_comment; }
466
467    /**
468     * Set the comment of the document
469     *
470     * @param $newComment string new comment of document
471     * @return bool
472     */
473    public function setComment($newComment) { /* {{{ */
474        $db = $this->_dms->getDB();
475
476        /* Check if 'onPreSetComment' callback is set */
477        if(isset($this->_dms->callbacks['onPreSetComment'])) {
478            foreach($this->_dms->callbacks['onPreSetComment'] as $callback) {
479                $ret = call_user_func($callback[0], $callback[1], $this, $newComment);
480                if(is_bool($ret))
481                    return $ret;
482            }
483        }
484
485        $queryStr = "UPDATE `tblDocuments` SET `comment` = ".$db->qstr($newComment)." WHERE `id` = ". $this->_id;
486        if (!$db->getResult($queryStr))
487            return false;
488
489        $oldComment = $this->_comment;
490        $this->_comment = $newComment;
491
492        /* Check if 'onPostSetComment' callback is set */
493        if(isset($this->_dms->callbacks['onPostSetComment'])) {
494            foreach($this->_dms->callbacks['onPostSetComment'] as $callback) {
495                $ret = call_user_func($callback[0], $callback[1], $this, $oldComment);
496                if(is_bool($ret))
497                    return $ret;
498            }
499        }
500
501        return true;
502    } /* }}} */
503
504    /**
505     * @return string
506     */
507    public function getKeywords() { return $this->_keywords; }
508
509    /**
510     * @param string $newKeywords
511     * @return bool
512     */
513    public function setKeywords($newKeywords) { /* {{{ */
514        $db = $this->_dms->getDB();
515
516        /* Check if 'onPreSetKeywords' callback is set */
517        if(isset($this->_dms->callbacks['onPreSetKeywords'])) {
518            foreach($this->_dms->callbacks['onPreSetKeywords'] as $callback) {
519                $ret = call_user_func($callback[0], $callback[1], $this, $newKeywords);
520                if(is_bool($ret))
521                    return $ret;
522            }
523        }
524
525        $queryStr = "UPDATE `tblDocuments` SET `keywords` = ".$db->qstr($newKeywords)." WHERE `id` = ". $this->_id;
526        if (!$db->getResult($queryStr))
527            return false;
528
529        $oldKeywords = $this->_keywords;
530        $this->_keywords = $newKeywords;
531
532        /* Check if 'onPostSetKeywords' callback is set */
533        if(isset($this->_dms->callbacks['onPostSetKeywords'])) {
534            foreach($this->_dms->callbacks['onPostSetKeywords'] as $callback) {
535                $ret = call_user_func($callback[0], $callback[1], $this, $oldKeywords);
536                if(is_bool($ret))
537                    return $ret;
538            }
539        }
540
541        return true;
542    } /* }}} */
543
544    /**
545     * Check if document has a given category
546     *
547     * @param SeedDMS_Core_DocumentCategory $cat
548     * @return bool true if document has category, otherwise false
549     */
550    public function hasCategory($cat) { /* {{{ */
551        $db = $this->_dms->getDB();
552
553        if(!$cat)
554            return false;
555
556        $queryStr = "SELECT * FROM `tblDocumentCategory` WHERE `documentID` = ".$this->_id." AND `categoryID`=".$cat->getId();
557        $resArr = $db->getResultArray($queryStr);
558        if (!$resArr)
559            return false;
560
561        return true;
562    } /* }}} */
563
564    /**
565     * Retrieve a list of all categories this document belongs to
566     *
567     * @return bool|SeedDMS_Core_DocumentCategory[]
568     */
569    public function getCategories() { /* {{{ */
570        $db = $this->_dms->getDB();
571
572        if(!$this->_categories) {
573            $queryStr = "SELECT * FROM `tblCategory` WHERE `id` IN (SELECT `categoryID` FROM `tblDocumentCategory` WHERE `documentID` = ".$this->_id.")";
574            $resArr = $db->getResultArray($queryStr);
575            if (is_bool($resArr) && !$resArr)
576                return false;
577
578            $this->_categories = [];
579            foreach ($resArr as $row) {
580                $cat = new SeedDMS_Core_DocumentCategory($row['id'], $row['name']);
581                $cat->setDMS($this->_dms);
582                $this->_categories[] = $cat;
583            }
584        }
585        return $this->_categories;
586    } /* }}} */
587
588    /**
589     * Set a list of categories for the document
590     *
591     * This method will delete currently assigned categories and sets new
592     * categories.
593     *
594     * @param SeedDMS_Core_DocumentCategory[] $newCategories list of category objects
595     * @return bool
596     */
597    public function setCategories($newCategories) { /* {{{ */
598        $db = $this->_dms->getDB();
599
600        /* Check if 'onPreSetCategories' callback is set */
601        if(isset($this->_dms->callbacks['onPreSetCategories'])) {
602            foreach($this->_dms->callbacks['onPreSetCategories'] as $callback) {
603                $ret = call_user_func($callback[0], $callback[1], $this, $newCategories);
604                if(is_bool($ret))
605                    return $ret;
606            }
607        }
608
609        $db->startTransaction();
610        $queryStr = "DELETE FROM `tblDocumentCategory` WHERE `documentID` = ". $this->_id;
611        if (!$db->getResult($queryStr)) {
612            $db->rollbackTransaction();
613            return false;
614        }
615
616        foreach($newCategories as $cat) {
617            $queryStr = "INSERT INTO `tblDocumentCategory` (`categoryID`, `documentID`) VALUES (". $cat->getId() .", ". $this->_id .")";
618            if (!$db->getResult($queryStr)) {
619                $db->rollbackTransaction();
620                return false;
621            }
622        }
623
624        $db->commitTransaction();
625
626        $oldCategories = $this->_categories;
627        $this->_categories = $newCategories;
628
629        /* Check if 'onPostSetCategories' callback is set */
630        if(isset($this->_dms->callbacks['onPostSetCategories'])) {
631            foreach($this->_dms->callbacks['onPostSetCategories'] as $callback) {
632                $ret = call_user_func($callback[0], $callback[1], $this, $oldCategories);
633                if(is_bool($ret))
634                    return $ret;
635            }
636        }
637
638        return true;
639    } /* }}} */
640
641    /**
642     * Add a list of categories to the document
643     *
644     * This method will add a list of new categories to the document.
645     *
646     * @param array $newCategories list of category objects
647     */
648    public function addCategories($newCategories) { /* {{{ */
649        $db = $this->_dms->getDB();
650
651        /* Check if 'onPreAddCategories' callback is set */
652        if(isset($this->_dms->callbacks['onPreAddCategories'])) {
653            foreach($this->_dms->callbacks['onPreAddCategories'] as $callback) {
654                $ret = call_user_func($callback[0], $callback[1], $this, $newCategories);
655                if(is_bool($ret))
656                    return $ret;
657            }
658        }
659
660        if(!$this->_categories)
661            $this->getCategories();
662
663        $catids = array();
664        foreach($this->_categories as $cat)
665            $catids[] = $cat->getID();
666
667        $db->startTransaction();
668        $ncat = array(); // Array containing actually added new categories
669        foreach($newCategories as $cat) {
670            if(!in_array($cat->getID(), $catids)) {
671                $queryStr = "INSERT INTO `tblDocumentCategory` (`categoryID`, `documentID`) VALUES (". $cat->getId() .", ". $this->_id .")";
672                if (!$db->getResult($queryStr)) {
673                    $db->rollbackTransaction();
674                    return false;
675                }
676                $ncat[] = $cat;
677            }
678        }
679        $db->commitTransaction();
680
681        $oldCategories = $this->_categories;
682        $this->_categories = array_merge($this->_categories, $ncat);
683
684        /* Check if 'onPostAddCategories' callback is set */
685        if(isset($this->_dms->callbacks['onPostAddCategories'])) {
686            foreach($this->_dms->callbacks['onPostAddCategories'] as $callback) {
687                $ret = call_user_func($callback[0], $callback[1], $this, $oldCategories);
688                if(is_bool($ret))
689                    return $ret;
690            }
691        }
692
693        return true;
694    } /* }}} */
695
696    /**
697     * Remove a list of categories from the document
698     *
699     * This method will remove a list of assigned categories to the document.
700     *
701     * @param array $newCategories list of category objects
702     */
703    public function removeCategories($categories) { /* {{{ */
704        $db = $this->_dms->getDB();
705
706        /* Check if 'onPreRemoveCategories' callback is set */
707        if(isset($this->_dms->callbacks['onPreRemoveCategories'])) {
708            foreach($this->_dms->callbacks['onPreRemoveCategories'] as $callback) {
709                $ret = call_user_func($callback[0], $callback[1], $this, $categories);
710                if(is_bool($ret))
711                    return $ret;
712            }
713        }
714
715        $catids = array();
716        foreach($categories as $cat)
717            $catids[] = $cat->getID();
718
719        $queryStr = "DELETE FROM `tblDocumentCategory` WHERE `documentID` = ". $this->_id ." AND `categoryID` IN (".implode(',', $catids).")";
720        if (!$db->getResult($queryStr)) {
721            return false;
722        }
723
724        $oldCategories = $this->_categories;
725        $this->_categories = null;
726
727        /* Check if 'onPostRemoveCategories' callback is set */
728        if(isset($this->_dms->callbacks['onPostRemoveCategories'])) {
729            foreach($this->_dms->callbacks['onPostRemoveCategories'] as $callback) {
730                $ret = call_user_func($callback[0], $callback[1], $this, $oldCategories);
731                if(is_bool($ret))
732                    return $ret;
733            }
734        }
735
736        return true;
737    } /* }}} */
738
739    /**
740     * Return creation date of the document
741     *
742     * @return integer unix timestamp of creation date
743     */
744    public function getDate() { /* {{{ */
745        return $this->_date;
746    } /* }}} */
747
748    /**
749     * Set creation date of the document
750     *
751     * @param integer $date timestamp of creation date. If false then set it
752     * to the current timestamp
753     * @return boolean true on success
754     */
755    public function setDate($date) { /* {{{ */
756        $db = $this->_dms->getDB();
757
758        if(!$date)
759            $date = time();
760        else {
761            if(!is_numeric($date))
762                return false;
763        }
764
765        $queryStr = "UPDATE `tblDocuments` SET `date` = " . (int) $date . " WHERE `id` = ". $this->_id;
766        if (!$db->getResult($queryStr))
767            return false;
768        $this->_date = $date;
769        return true;
770    } /* }}} */
771
772    /**
773     * Check, if this document is a child of a given folder
774     *
775     * @param object $folder parent folder
776     * @return boolean true if document is a direct child of the given folder
777     */
778    public function isDescendant($folder) { /* {{{ */
779        /* First check if the parent folder is folder looking for */
780        if ($this->getFolder()->getID() == $folder->getID())
781            return true;
782        /* Second, check for the parent folder of this document to be
783         * below the given folder
784         */
785        if($this->getFolder()->isDescendant($folder))
786            return true;
787        return false;
788    } /* }}} */
789
790    /**
791     * Return the parent folder of the document
792     *
793     * @see SeedDMS_Core_Document::getFolder()
794     *
795     * @return SeedDMS_Core_Folder parent folder
796     */
797    public function getParent() { /* {{{ */
798        return $this->getFolder();
799    } /* }}} */
800
801    /**
802     * Return the parent folder of the document
803     *
804     * @return SeedDMS_Core_Folder parent folder
805     */
806    public function getFolder() { /* {{{ */
807        if (!isset($this->_folder))
808            $this->_folder = $this->_dms->getFolder($this->_folderID);
809        return $this->_folder;
810    } /* }}} */
811
812    /**
813     * Set folder of a document
814     *
815     * This method basically moves a document from a folder to another
816     * folder.
817     *
818     * @param SeedDMS_Core_Folder $newFolder
819     * @return boolean false in case of an error, otherwise true
820     */
821    public function setParent($newFolder) { /* {{{ */
822        return $this->setFolder($newFolder);
823    } /* }}} */
824
825    /**
826     * Set folder of a document
827     *
828     * This method basically moves a document from a folder to another
829     * folder.
830     *
831     * @param SeedDMS_Core_Folder $newFolder
832     * @return boolean false in case of an error, otherwise true
833     */
834    public function setFolder($newFolder) { /* {{{ */
835        $db = $this->_dms->getDB();
836
837        if(!$newFolder)
838            return false;
839
840        if(!$newFolder->isType('folder'))
841            return false;
842
843        /* Check if 'onPreSetFolder' callback is set */
844        if(isset($this->_dms->callbacks['onPreSetFolder'])) {
845            foreach($this->_dms->callbacks['onPreSetFolder'] as $callback) {
846                $ret = call_user_func($callback[0], $callback[1], $this, $newFolder);
847                if(is_bool($ret))
848                    return $ret;
849            }
850        }
851
852        $db->startTransaction();
853
854        $queryStr = "UPDATE `tblDocuments` SET `folder` = " . $newFolder->getID() . " WHERE `id` = ". $this->_id;
855        if (!$db->getResult($queryStr)) {
856            $db->rollbackTransaction();
857            return false;
858        }
859
860        // Make sure that the folder search path is also updated.
861        $path = $newFolder->getPath();
862        $flist = "";
863        /** @var SeedDMS_Core_Folder[] $path */
864        foreach ($path as $f) {
865            $flist .= ":".$f->getID();
866        }
867        if (strlen($flist)>1) {
868            $flist .= ":";
869        }
870        $queryStr = "UPDATE `tblDocuments` SET `folderList` = '" . $flist . "' WHERE `id` = ". $this->_id;
871        if (!$db->getResult($queryStr)) {
872            $db->rollbackTransaction();
873            return false;
874        }
875
876        $db->commitTransaction();
877
878        $oldFolder = $this->_folder;
879        $this->_folderID = $newFolder->getID();
880        $this->_folder = $newFolder;
881
882        /* Check if 'onPostSetFolder' callback is set */
883        if(isset($this->_dms->callbacks['onPostSetFolder'])) {
884            foreach($this->_dms->callbacks['onPostSetFolder'] as $callback) {
885                $ret = call_user_func($callback[0], $callback[1], $this, $oldFolder);
886                if(is_bool($ret))
887                    return $ret;
888            }
889        }
890
891        return true;
892    } /* }}} */
893
894    /**
895     * Return owner of document
896     *
897     * @return SeedDMS_Core_User owner of document as an instance of {@see SeedDMS_Core_User}
898     */
899    public function getOwner() { /* {{{ */
900        if (!isset($this->_owner))
901            $this->_owner = $this->_dms->getUser($this->_ownerID);
902        return $this->_owner;
903    } /* }}} */
904
905    /**
906     * Set owner of a document
907     *
908     * @param SeedDMS_Core_User $newOwner new owner
909     * @return boolean true if successful otherwise false
910     */
911    public function setOwner($newOwner) { /* {{{ */
912        $db = $this->_dms->getDB();
913
914        if(!$newOwner)
915            return false;
916
917        if(!$newOwner->isType('user'))
918            return false;
919
920        $oldOwner = self::getOwner();
921
922        $db->startTransaction();
923
924        /* Check if 'onPreSetOwner' callback is set */
925        if(isset($this->_dms->callbacks['onPreSetOwner'])) {
926            foreach($this->_dms->callbacks['onPreSetOwner'] as $callback) {
927                $ret = call_user_func($callback[0], $callback[1], $this, $newOwner);
928                if(is_bool($ret))
929                    return $ret;
930            }
931        }
932
933        $queryStr = "UPDATE `tblDocuments` set `owner` = " . $newOwner->getID() . " WHERE `id` = " . $this->_id;
934        if (!$db->getResult($queryStr)) {
935            $db->rollbackTransaction();
936            return false;
937        }
938
939        /* FIXME: Update also all locks and checkouts done by the previous owner */
940        /*
941        $queryStr = "UPDATE `tblDocumentLocks` set `userID` = " . $newOwner->getID() . " WHERE `document` = " . $this->_id . " AND `userID` = " . $oldOwner->getID();
942        if (!$db->getResult($queryStr)) {
943            $db->rollbackTransaction();
944            return false;
945        }
946
947        $queryStr = "UPDATE `tblDocumentCheckOuts` set `userID` = " . $newOwner->getID() . " WHERE `document` = " . $this->_id . " AND `userID` = " . $oldOwner->getID();
948        if (!$db->getResult($queryStr)) {
949            $db->rollbackTransaction();
950            return false;
951        }
952         */
953
954        $db->commitTransaction();
955
956        $this->_ownerID = $newOwner->getID();
957        $this->_owner = $newOwner;
958
959        $this->_readAccessList = array();
960
961        /* Check if 'onPostSetOwner' callback is set */
962        if(isset($this->_dms->callbacks['onPostSetOwner'])) {
963            foreach($this->_dms->callbacks['onPostSetOwner'] as $callback) {
964                $ret = call_user_func($callback[0], $callback[1], $this, $oldOwner);
965                if(is_bool($ret))
966                    return $ret;
967            }
968        }
969
970        return true;
971    } /* }}} */
972
973    /**
974     * @return bool|int
975     */
976    public function getDefaultAccess() { /* {{{ */
977        if ($this->inheritsAccess()) {
978            $res = $this->getFolder();
979            if (!$res) return false;
980            return $this->_folder->getDefaultAccess();
981        }
982        return $this->_defaultAccess;
983    } /* }}} */
984
985    /**
986     * Set default access mode
987     *
988     * This method sets the default access mode and also removes all notifiers which
989     * will not have read access anymore. Setting a default access mode will only
990     * have an immediate effect if the access rights are not inherited, otherwise
991     * it just updates the database record of the document and once the
992     * inheritance is turn off the default access mode will take effect.
993     *
994     * @param integer     $mode    access mode
995     * @param bool|string $noclean set to true if notifier list shall not be clean up
996     *
997     * @return bool
998     */
999    public function setDefaultAccess($mode, $noclean=false) { /* {{{ */
1000        $db = $this->_dms->getDB();
1001
1002        if($mode < M_LOWEST_RIGHT || $mode > M_HIGHEST_RIGHT)
1003            return false;
1004
1005        $queryStr = "UPDATE `tblDocuments` set `defaultAccess` = " . (int) $mode . " WHERE `id` = " . $this->_id;
1006        if (!$db->getResult($queryStr))
1007            return false;
1008
1009        $this->_defaultAccess = $mode;
1010        $this->_readAccessList = array();
1011
1012        /* Setting the default access mode does not have any effect if access
1013         * is still inherited. In that case there is no need to clean the
1014         * notification list.
1015         */
1016        if(!$noclean && !$this->_inheritAccess)
1017            $this->cleanNotifyList();
1018
1019        return true;
1020    } /* }}} */
1021
1022    /**
1023     * @return bool
1024     */
1025    public function inheritsAccess() { return $this->_inheritAccess; }
1026
1027    /**
1028     * This is supposed to be a replacement for inheritsAccess()
1029     *
1030     * @return bool
1031     */
1032    public function getInheritAccess() { return $this->_inheritAccess; }
1033
1034    /**
1035     * Set inherited access mode
1036     *
1037     * Setting inherited access mode will set or unset the internal flag which
1038     * controls if the access mode is inherited from the parent folder or not.
1039     * It will not modify the
1040     * access control list for the current object. It will remove all
1041     * notifications of users which do not even have read access anymore
1042     * after setting or unsetting inherited access.
1043     *
1044     * @param boolean $inheritAccess set to true for setting and false for
1045     *        unsetting inherited access mode
1046     * @param boolean $noclean set to true if notifier list shall not be clean up
1047     * @return boolean true if operation was successful otherwise false
1048     */
1049    public function setInheritAccess($inheritAccess, $noclean=false) { /* {{{ */
1050        $db = $this->_dms->getDB();
1051
1052        $queryStr = "UPDATE `tblDocuments` SET `inheritAccess` = " . ($inheritAccess ? "1" : "0") . " WHERE `id` = " . $this->_id;
1053        if (!$db->getResult($queryStr))
1054            return false;
1055
1056        $this->_inheritAccess = ($inheritAccess ? true : false);
1057        $this->_readAccessList = array();
1058
1059        if(!$noclean)
1060            $this->cleanNotifyList();
1061
1062        return true;
1063    } /* }}} */
1064
1065    /**
1066     * Check if document expires
1067     *
1068     * @return boolean true if document has expiration date set, otherwise false
1069     */
1070    public function expires() { /* {{{ */
1071        if (intval($this->_expires) == 0)
1072            return false;
1073        else
1074            return true;
1075    } /* }}} */
1076
1077    /**
1078     * Get expiration time of document
1079     *
1080     * @return integer/boolean expiration date as unix timestamp or false
1081     */
1082    public function getExpires() { /* {{{ */
1083        if (intval($this->_expires) == 0)
1084            return false;
1085        else
1086            return $this->_expires;
1087    } /* }}} */
1088
1089    /**
1090     * Set expiration date as unix timestamp
1091     *
1092     * @param integer $expires unix timestamp of expiration date
1093     * @return bool
1094     */
1095    public function setExpires($expires) { /* {{{ */
1096        $db = $this->_dms->getDB();
1097
1098        $expires = (!$expires) ? 0 : $expires;
1099
1100        if ($expires == $this->_expires) {
1101            // No change is necessary.
1102            return true;
1103        }
1104
1105        $queryStr = "UPDATE `tblDocuments` SET `expires` = " . (int) $expires . " WHERE `id` = " . $this->_id;
1106        if (!$db->getResult($queryStr))
1107            return false;
1108
1109        $this->_expires = $expires;
1110        return true;
1111    } /* }}} */
1112
1113    /**
1114     * Check if the document has expired
1115     *
1116     * The method expects to database field 'expired' to hold the timestamp
1117     * of the start of day at which end the document expires. The document will
1118     * expire if that day is over. Hence, a document will *not* 
1119     * be expired during the day of expiration but at the end of that day
1120     *
1121     * @return boolean true if document has expired otherwise false
1122     */
1123    public function hasExpired() { /* {{{ */
1124        if (intval($this->_expires) == 0) return false;
1125        if (time()>=$this->_expires+24*60*60) return true;
1126        return false;
1127    } /* }}} */
1128
1129    /**
1130     * Check if the document has expired and set the status accordingly
1131     *
1132     * It will also recalculate the status if the current status is
1133     * set to S_EXPIRED but the document isn't actually expired.
1134     * The method will update the document status log database table
1135     * if needed.
1136     * FIXME: some left over reviewers/approvers are in the way if
1137     * no workflow is set and traditional workflow mode is on. In that
1138     * case the status is set to S_DRAFT_REV or S_DRAFT_APP
1139     *
1140     * @return boolean true if status has changed
1141     */
1142    public function verifyLastestContentExpriry(){ /* {{{ */
1143        $lc=$this->getLatestContent();
1144        if($lc) {
1145            $st=$lc->getStatus();
1146
1147            if (($st["status"]==S_DRAFT_REV || $st["status"]==S_DRAFT_APP || $st["status"]==S_IN_WORKFLOW || $st["status"]==S_RELEASED || $st["status"]==S_IN_REVISION) && $this->hasExpired()){
1148                return $lc->setStatus(S_EXPIRED,"", $this->getOwner());
1149            }
1150            elseif ($st["status"]==S_EXPIRED && !$this->hasExpired() ){
1151                $lc->verifyStatus(true, $this->getOwner());
1152                return true;
1153            }
1154        }
1155        return false;
1156    } /* }}} */
1157
1158    /**
1159     * Check if latest content of the document has a scheduled
1160     * revision workflow.
1161     *
1162     * This method was moved into SeedDMS_Core_DocumentContent and
1163     * the original method in SeedDMS_Core_Document now uses it for
1164     * the latest version.
1165     *
1166     * @param object $user user requesting the possible automatic change
1167     * @param string $next next date for review
1168     * @return boolean true if status has changed
1169     */
1170    function checkForDueRevisionWorkflow($user, $next=''){ /* {{{ */
1171        $lc=$this->getLatestContent();
1172        if($lc) {
1173            return $lc->checkForDueRevisionWorkflow($user, $next);
1174        }
1175        return false;
1176    } /* }}} */
1177
1178    /**
1179     * Check if document is locked
1180     *
1181     * @return boolean true if locked otherwise false
1182     */
1183    public function isLocked() { return $this->_locked != -1; }
1184
1185    /**
1186     * Lock or unlock document
1187     *
1188     * @param SeedDMS_Core_User|bool $falseOrUser user object for locking or false for unlocking
1189     * @return boolean true if operation was successful otherwise false
1190     */
1191    public function setLocked($falseOrUser) { /* {{{ */
1192        $db = $this->_dms->getDB();
1193
1194        $lockUserID = -1;
1195        if (is_bool($falseOrUser) && !$falseOrUser) {
1196            $queryStr = "DELETE FROM `tblDocumentLocks` WHERE `document` = ".$this->_id;
1197        }
1198        else if (is_object($falseOrUser)) {
1199            $queryStr = "INSERT INTO `tblDocumentLocks` (`document`, `userID`) VALUES (".$this->_id.", ".$falseOrUser->getID().")";
1200            $lockUserID = $falseOrUser->getID();
1201        }
1202        else {
1203            return false;
1204        }
1205        if (!$db->getResult($queryStr)) {
1206            return false;
1207        }
1208        unset($this->_lockingUser);
1209        $this->_locked = $lockUserID;
1210        return true;
1211    } /* }}} */
1212
1213    /**
1214     * Get the user currently locking the document
1215     *
1216     * @return SeedDMS_Core_User|bool user have a lock
1217     */
1218    public function getLockingUser() { /* {{{ */
1219        if (!$this->isLocked())
1220            return false;
1221
1222        if (!isset($this->_lockingUser))
1223            $this->_lockingUser = $this->_dms->getUser($this->_locked);
1224        return $this->_lockingUser;
1225    } /* }}} */
1226
1227    /**
1228     * Check if document is checked out
1229     *
1230     * @return boolean true if checked out otherwise false
1231     */
1232    function isCheckedOut() { /* {{{ */
1233        $db = $this->_dms->getDB();
1234
1235        $queryStr = "SELECT * FROM `tblDocumentCheckOuts` WHERE `document` = " . (int) $this->_id;
1236        $resArr = $db->getResultArray($queryStr);
1237        if ((is_bool($resArr) && $resArr==false) || (count($resArr)==0)) {
1238            // Could not find a check out for the selected document.
1239            return false;
1240        } else {
1241            // A check out has been identified for this document.
1242            return true;
1243        }
1244    } /* }}} */
1245
1246    /**
1247     * Get checkout info for document
1248     *
1249     * This returns the checkouts for a document. There could be several checkouts
1250     * for one document, but usually there is just one.
1251     *
1252     * @return array/boolean records from table tblDocumentCheckOuts or false
1253     * in case of an error.
1254     */
1255    function getCheckOutInfo() { /* {{{ */
1256        $db = $this->_dms->getDB();
1257
1258        $queryStr = "SELECT * FROM `tblDocumentCheckOuts` WHERE `document` = " . (int) $this->_id;
1259        $resArr = $db->getResultArray($queryStr);
1260        if ((is_bool($resArr) && $resArr==false) || (count($resArr)==0)) {
1261            // Could not find a check out for the selected document.
1262            return false;
1263        } else {
1264            // A check out has been identified for this document.
1265            return $resArr;
1266        }
1267    } /* }}} */
1268
1269
1270    /**
1271     * Check out document
1272     *
1273     * Creates a check out record for the document and copies the latest
1274     * version of the document into the given checkout dir.
1275     *
1276     * @param object $user object of user doing the checkout
1277     * @param string $checkoutdir directory where the file will be placed
1278     * @return object object of class SeedDMS_Core_DocumentCheckOut
1279     */
1280    function checkOut($user, $checkoutdir) { /* {{{ */
1281        $db = $this->_dms->getDB();
1282
1283        if(self::isCheckedOut())
1284            return false;
1285
1286        /* Check if checkout dir is writable */
1287        if(!file_exists($checkoutdir)) {
1288            return false;
1289        }
1290
1291        $db->startTransaction();
1292
1293        $lc = self::getLatestContent();
1294
1295        $ext = pathinfo($this->getName(), PATHINFO_EXTENSION);
1296        $oext = pathinfo($lc->getOriginalFileName(), PATHINFO_EXTENSION);
1297        if($ext == $oext)
1298            $filename = preg_replace('/[^A-Za-z0-9_.-]/', '_', $this->getName());
1299        else {
1300            $filename = preg_replace('/[^A-Za-z0-9_-]/', '_', $this->getName()).'.'.$oext;
1301        }
1302        $filename = $checkoutdir.$this->getID().'-'.$lc->getVersion().'-'.$filename; //$lc->getOriginalFileName();
1303        $queryStr = "INSERT INTO `tblDocumentCheckOuts` (`document`, `version`, `userID`, `date`, `filename`) VALUES (".$this->_id.", ".$lc->getVersion().", ".$user->getID().", ".$db->getCurrentDatetime().", ".$db->qstr($filename).")";
1304        if (!$db->getResult($queryStr))
1305            return false;
1306
1307        /* Try to copy the file */
1308        $err = SeedDMS_Core_File::copyFile($this->_dms->contentDir . $this->getDir() . $lc->getFileName(), $filename);
1309        if (!$err) {
1310            $db->rollbackTransaction();
1311            return false;
1312        }
1313
1314        $db->commitTransaction();
1315        return true;
1316    } /* }}} */
1317
1318    /**
1319     * Check in document
1320     *
1321     * Î¤his function is similar to SeedDMS_Core_Document::addContent()
1322     * but reads the content from the file which was previously checked out.
1323     * Internal this method calls
1324     * SeedDMS_Core_Document::addContent() but takes over the original
1325     * filename, filetype and mimetype from the checked out version.
1326     * No matter in which state the current checked out file is, the
1327     * document will be checked back in afterwards.
1328     *
1329     * There are various reason why a check in may fail. In those cases
1330     * this method will return false, but if the checked out document has
1331     * disappeared, the checkout will be ended and the method returns true
1332     * without creating a new version.
1333     *
1334     * The check in may not be done by the user who has done the check out,
1335     * but if it is a different user, this user must have unlimited access
1336     * on the document.
1337     *
1338     * @param string $comment
1339     * @param object $user
1340     * @param array $reviewers
1341     * @param array $approvers
1342     * @param integer $version
1343     * @param array $attributes
1344     * @param object $workflow
1345     * @param integer $initstate intial document status
1346     * @return boolean|object false in case of error, true if no error occurs but
1347     * the document remains unchanged (because the checked out file has not
1348     * changed or it has disappeared and couldnt't be checked in), or
1349     * an instance of class SeedDMS_Core_AddContentResultSet if the document
1350     * was updated.
1351     */
1352    function checkIn($comment, $user, $reviewers=array(), $approvers=array(), $version=0, $attributes=array(), $workflow=null, $initstate=S_RELEASED) { /* {{{ */
1353        $db = $this->_dms->getDB();
1354
1355        $infos = self::getCheckOutInfo();
1356        if(!$infos)
1357            return false;
1358        $info = $infos[0];
1359        $lc = self::getLatestContent();
1360
1361        /* If file doesn't exist anymore, then just remove the record from the db */
1362        if(!file_exists($info['filename'])) {
1363            $queryStr = "DELETE FROM `tblDocumentCheckOuts` WHERE `document` = ".$this->_id;
1364            $db->getResult($queryStr);
1365            return true;
1366        }
1367
1368        /* Check if version of checked out file is equal to current version */
1369        if($lc->getVersion() != $info['version']) {
1370            return false;
1371        }
1372
1373        /* Check if the user doing the check in is the same use as the one
1374         * have done the check out or at least have unlimited rights on the
1375         * document.
1376         */
1377        if($user->getID() != $info['userID'] && $this->getAccessMode($user) < M_ALL) {
1378            return false;
1379        }
1380
1381        $content = true;
1382        /* Do not create a new version if the file was unchanged */
1383        $checksum = SeedDMS_Core_File::checksum($info['filename']);
1384        if($checksum != $lc->getChecksum()) {
1385            $content = $this->addContent($comment, $user, $info['filename'], $lc->getOriginalFileName(), $lc->getFileType(), $lc->getMimeType(), $reviewers, $approvers, $version, $attributes, $workflow, $initstate);
1386            if($content) {
1387                if(!$this->_dms->forceRename) {
1388                    SeedDMS_Core_File::removeFile($info['filename']);
1389                }
1390                $queryStr = "DELETE FROM `tblDocumentCheckOuts` WHERE `document` = ".$this->_id;
1391                $db->getResult($queryStr);
1392                return $content;
1393            } else {
1394                return false;
1395            }
1396        } else {
1397            SeedDMS_Core_File::removeFile($info['filename']);
1398            $queryStr = "DELETE FROM `tblDocumentCheckOuts` WHERE `document` = ".$this->_id;
1399            $db->getResult($queryStr);
1400            return true;
1401        }
1402    } /* }}} */
1403
1404    /**
1405     * Cancel check out of document
1406     *
1407     * This function will cancel a check out in progress by removing
1408     * the check out record from the database and removing the file
1409     * from the check out folder.
1410     *
1411     * @return boolean true if cancelation was successful
1412     */
1413    function cancelCheckOut() { /* {{{ */
1414        $db = $this->_dms->getDB();
1415
1416        $infos = self::getCheckOutInfo();
1417        if($infos) {
1418            $info = $infos[0];
1419
1420            $db->startTransaction();
1421            $queryStr = "DELETE FROM `tblDocumentCheckOuts` WHERE `document` = ".$this->_id;
1422            if (!$db->getResult($queryStr)) {
1423                $db->rollbackTransaction();
1424                return false;
1425            }
1426            if(file_exists($info['filename']) && !SeedDMS_Core_File::removeFile($info['filename'])) {
1427                $db->rollbackTransaction();
1428                return false;
1429            }
1430            $db->commitTransaction();
1431        }
1432
1433        return true;
1434
1435    } /* }}} */
1436
1437    /**
1438     * Return the check out status of the document
1439     *
1440     * This method returns the checkout status of a previosly checked out
1441     * document. If a document was checked out more then once, the parameter
1442     * $index can be passed to retrieve a certain checkout info.
1443     *
1444     * @return int 1=The checked out file doesn't exists anymore,
1445     * 2=The checked out version doesn't exists anymore
1446     * 3=The checked out file has not been modified yet
1447     * 4=new check out record in database found
1448     * 0=The checked out file is modified and check in will create a new version
1449     */
1450    function checkOutStatus($index=0) { /* {{{ */
1451        $infos = self::getCheckOutInfo();
1452        if(!$infos || !isset($infos[$index]))
1453            return 4;
1454
1455        $info = $infos[$index];
1456        $lc = self::getLatestContent();
1457
1458        /* If file doesn't exist anymore, then just remove the record from the db */
1459        if(!file_exists($info['filename'])) {
1460            return 1;
1461        }
1462
1463        /* Check if version of checked out file is equal to current version */
1464        if($lc->getVersion() != $info['version']) {
1465            return 2;
1466        }
1467
1468        $checksum = SeedDMS_Core_File::checksum($info['filename']);
1469        if($checksum == $lc->getChecksum()) {
1470            return 3;
1471        }
1472
1473        return 0;
1474    } /* }}} */
1475
1476    /**
1477     * @return float
1478     */
1479    public function getSequence() { return $this->_sequence; }
1480
1481    /**
1482     * @param float $seq
1483     * @return bool
1484     */
1485    public function setSequence($seq) { /* {{{ */
1486        $db = $this->_dms->getDB();
1487
1488        $queryStr = "UPDATE `tblDocuments` SET `sequence` = " . $seq . " WHERE `id` = " . $this->_id;
1489        if (!$db->getResult($queryStr))
1490            return false;
1491
1492        $this->_sequence = $seq;
1493        return true;
1494    } /* }}} */
1495
1496    /**
1497     * Delete all entries for this document from the access control list
1498     *
1499     * @param boolean $noclean set to true if notifier list shall not be clean up
1500     * @return boolean true if operation was successful otherwise false
1501     */
1502    public function clearAccessList($noclean=false) { /* {{{ */
1503        $db = $this->_dms->getDB();
1504
1505        $queryStr = "DELETE FROM `tblACLs` WHERE `targetType` = " . T_DOCUMENT . " AND `target` = " . $this->_id;
1506        if (!$db->getResult($queryStr))
1507            return false;
1508
1509        unset($this->_accessList);
1510        $this->_readAccessList = array();
1511
1512        if(!$noclean)
1513            $this->cleanNotifyList();
1514
1515        return true;
1516    } /* }}} */
1517
1518    /**
1519     * Returns a list of access privileges
1520     *
1521     * If the document inherits the access privileges from the parent folder
1522     * those will be returned.
1523     * $mode and $op can be set to restrict the list of returned access
1524     * privileges. If $mode is set to M_ANY no restriction will apply
1525     * regardless of the value of $op. The returned array contains a list
1526     * of {@see SeedDMS_Core_UserAccess} and
1527     * {@see SeedDMS_Core_GroupAccess} objects. Even if the document
1528     * has no access list the returned array contains the two elements
1529     * 'users' and 'groups' which are than empty. The methode returns false
1530     * if the function fails.
1531     *
1532     * @param int $mode access mode (defaults to M_ANY)
1533     * @param int|string $op operation (defaults to O_EQ)
1534     * @return bool|array
1535     */
1536    public function getAccessList($mode = M_ANY, $op = O_EQ) { /* {{{ */
1537        $db = $this->_dms->getDB();
1538
1539        if ($this->inheritsAccess()) {
1540            $res = $this->getFolder();
1541            if (!$res) return false;
1542            $pacl = $res->getAccessList($mode, $op);
1543            return $pacl;
1544        } else {
1545            $pacl = array("groups" => array(), "users" => array());
1546        }
1547
1548        if (!isset($this->_accessList[$mode])) {
1549            if ($op!=O_GTEQ && $op!=O_LTEQ && $op!=O_EQ) {
1550                return false;
1551            }
1552            $modeStr = "";
1553            if ($mode!=M_ANY) {
1554                $modeStr = " AND `mode`".$op.(int)$mode;
1555            }
1556            $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT.
1557                " AND `target` = " . $this->_id . $modeStr . " ORDER BY `targetType`";
1558            $resArr = $db->getResultArray($queryStr);
1559            if (is_bool($resArr) && !$resArr)
1560                return false;
1561
1562            $this->_accessList[$mode] = array("groups" => array(), "users" => array());
1563            foreach ($resArr as $row) {
1564                if ($row["userID"] != -1)
1565                    array_push($this->_accessList[$mode]["users"], new SeedDMS_Core_UserAccess($this->_dms->getUser($row["userID"]), (int) $row["mode"]));
1566                else //if ($row["groupID"] != -1)
1567                    array_push($this->_accessList[$mode]["groups"], new SeedDMS_Core_GroupAccess($this->_dms->getGroup($row["groupID"]), (int) $row["mode"]));
1568            }
1569        }
1570
1571        return $this->_accessList[$mode];
1572        return SeedDMS_Core_DMS::mergeAccessLists($pacl, $this->_accessList[$mode]);
1573    } /* }}} */
1574
1575    /**
1576     * Add access right to document
1577     *
1578     * This method may change in the future. Instead of passing a flag
1579     * and a user/group id a user or group object will be expected.
1580     * Starting with version 5.1.25 this method will first check if there
1581     * is already an access right for the user/group.
1582     *
1583     * @param integer $mode access mode
1584     * @param integer $userOrGroupID id of user or group
1585     * @param integer $isUser set to 1 if $userOrGroupID is the id of a
1586     *        user otherwise it will be considered a group id
1587     * @return bool true on success, otherwise false
1588     */
1589    public function addAccess($mode, $userOrGroupID, $isUser) { /* {{{ */
1590        $db = $this->_dms->getDB();
1591
1592        if($mode < M_NONE || $mode > M_ALL)
1593            return false;
1594
1595        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1596
1597        /* Adding a second access right will return false */
1598        $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT.
1599                " AND `target` = " . $this->_id . " AND ". $userOrGroup . " = ".$userOrGroupID;
1600        $resArr = $db->getResultArray($queryStr);
1601        if (is_bool($resArr) || $resArr)
1602            return false;
1603
1604        $queryStr = "INSERT INTO `tblACLs` (`target`, `targetType`, ".$userOrGroup.", `mode`) VALUES
1605                    (".$this->_id.", ".T_DOCUMENT.", " . (int) $userOrGroupID . ", " .(int) $mode. ")";
1606        if (!$db->getResult($queryStr))
1607            return false;
1608
1609        unset($this->_accessList);
1610        $this->_readAccessList = array();
1611
1612        // Update the notify list, if necessary.
1613        if ($mode == M_NONE) {
1614            $this->removeNotify($userOrGroupID, $isUser);
1615        }
1616
1617        return true;
1618    } /* }}} */
1619
1620    /**
1621     * Change access right of document
1622     *
1623     * This method may change in the future. Instead of passing a flag
1624     * and a user/group id a user or group object will be expected.
1625     *
1626     * @param integer $newMode access mode
1627     * @param integer $userOrGroupID id of user or group
1628     * @param integer $isUser set to 1 if $userOrGroupID is the id of a
1629     *        user otherwise it will be considered a group id
1630     * @return bool true on success, otherwise false
1631     */
1632    public function changeAccess($newMode, $userOrGroupID, $isUser) { /* {{{ */
1633        $db = $this->_dms->getDB();
1634
1635        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1636
1637        /* Get the old access right */
1638        $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT.
1639                " AND `target` = " . $this->_id . " AND ". $userOrGroup . " = ". (int) $userOrGroupID;
1640        $resArr = $db->getResultArray($queryStr);
1641        if (!$resArr)
1642            return false;
1643
1644        $oldmode = $resArr[0]['mode'];
1645
1646        $queryStr = "UPDATE `tblACLs` SET `mode` = " . (int) $newMode . " WHERE `targetType` = ".T_DOCUMENT." AND `target` = " . $this->_id . " AND " . $userOrGroup . " = " . (int) $userOrGroupID;
1647        if (!$db->getResult($queryStr))
1648            return false;
1649
1650        unset($this->_accessList);
1651        $this->_readAccessList = array();
1652
1653        // Update the notify list, if necessary.
1654        if ($newMode == M_NONE) {
1655            $this->removeNotify($userOrGroupID, $isUser);
1656        }
1657
1658        return $oldmode;
1659    } /* }}} */
1660
1661    /**
1662     * Remove access rights for a user or group
1663     *
1664     * @param integer $userOrGroupID ID of user or group
1665     * @param boolean $isUser true if $userOrGroupID is a user id, false if it
1666     *        is a group id.
1667     * @return boolean true on success, otherwise false
1668     */
1669    public function removeAccess($userOrGroupID, $isUser) { /* {{{ */
1670        $db = $this->_dms->getDB();
1671
1672        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1673
1674        /* Get the old access right */
1675        $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT.
1676                " AND `target` = " . $this->_id . " AND ". $userOrGroup . " = ". (int) $userOrGroupID;
1677        $resArr = $db->getResultArray($queryStr);
1678        if (!$resArr)
1679            return false;
1680
1681        $queryStr = "DELETE FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT." AND `target` = ".$this->_id." AND ".$userOrGroup." = " . (int) $userOrGroupID;
1682        if (!$db->getResult($queryStr))
1683            return false;
1684
1685        unset($this->_accessList);
1686        $this->_readAccessList = array();
1687
1688        // Update the notify list, if the user looses access rights.
1689        $mode = ($isUser ? $this->getAccessMode($this->_dms->getUser($userOrGroupID)) : $this->getGroupAccessMode($this->_dms->getGroup($userOrGroupID)));
1690        if ($mode == M_NONE) {
1691            $this->removeNotify($userOrGroupID, $isUser);
1692        }
1693
1694        return true;
1695    } /* }}} */
1696
1697    /**
1698     * Returns the greatest access privilege for a given user
1699     *
1700     * This method returns the access mode for a given user. An administrator
1701     * and the owner of the folder has unrestricted access. A guest user has
1702     * read only access or no access if access rights are further limited
1703     * by access control lists. All other users have access rights according
1704     * to the access control lists or the default access. This method will
1705     * recursive check for access rights of parent folders if access rights
1706     * are inherited.
1707     *
1708     * The function searches the access control list for entries of
1709     * user $user. If it finds more than one entry it will return the
1710     * one allowing the greatest privileges, but user rights will always
1711     * precede group rights. If there is no entry in the
1712     * access control list, it will return the default access mode.
1713     * The function takes inherited access rights into account.
1714     * For a list of possible access rights see @file inc.AccessUtils.php
1715     *
1716     * Having access on a document does not necessarily mean the document
1717     * content is accessible too. Accessing the content is checked by
1718     * {@see SeedDMS_Core_DocumentContent::getAccessMode()} which calls
1719     * a callback function defined by the application. If the callback
1720     * function is not set, access on the content is always granted.
1721     *
1722     * Before checking the access in the method itself a callback 'onCheckAccessDocument'
1723     * is called. If it returns a value > 0, then this will be returned by this
1724     * method without any further checks. The optional paramater $context
1725     * will be passed as a third parameter to the callback. It contains
1726     * the operation for which the access mode is retrieved. It is for example
1727     * set to 'removeDocument' if the access mode is used to check for sufficient
1728     * permission on deleting a document.
1729     *
1730     * @param $user object instance of class SeedDMS_Core_User
1731     * @param string $context context in which the access mode is requested
1732     * @return integer access mode
1733     */
1734    public function getAccessMode($user, $context='') { /* {{{ */
1735        if(!$user)
1736            return M_NONE;
1737
1738        /* Check if 'onCheckAccessDocument' callback is set */
1739        if(isset($this->_dms->callbacks['onCheckAccessDocument'])) {
1740            foreach($this->_dms->callbacks['onCheckAccessDocument'] as $callback) {
1741                if(($ret = call_user_func($callback[0], $callback[1], $this, $user, $context)) > 0) {
1742                    return $ret;
1743                }
1744            }
1745        }
1746
1747        /* Administrators have unrestricted access */
1748        if ($user->isAdmin()) return M_ALL;
1749
1750        /* The owner of the document has unrestricted access */
1751        if ($user->getID() == $this->_ownerID) return M_ALL;
1752
1753        /* Check ACLs */
1754        $accessList = $this->getAccessList();
1755        if (!$accessList) return false;
1756
1757        /** @var SeedDMS_Core_UserAccess $userAccess */
1758        foreach ($accessList["users"] as $userAccess) {
1759            if ($userAccess->getUserID() == $user->getID()) {
1760                $mode = $userAccess->getMode();
1761                if ($user->isGuest()) {
1762                    if ($mode >= M_READ) $mode = M_READ;
1763                }
1764                return $mode;
1765            }
1766        }
1767
1768        /* Get the highest right defined by a group */
1769        if($accessList['groups']) {
1770            $mode = 0;
1771            /** @var SeedDMS_Core_GroupAccess $groupAccess */
1772            foreach ($accessList["groups"] as $groupAccess) {
1773                if ($user->isMemberOfGroup($groupAccess->getGroup())) {
1774                    if ($groupAccess->getMode() > $mode)
1775                        $mode = $groupAccess->getMode();
1776                }
1777            }
1778            if($mode) {
1779                if ($user->isGuest()) {
1780                    if ($mode >= M_READ) $mode = M_READ;
1781                }
1782                return $mode;
1783            }
1784        }
1785
1786        $mode = $this->getDefaultAccess();
1787        if ($user->isGuest()) {
1788            if ($mode >= M_READ) $mode = M_READ;
1789        }
1790        return $mode;
1791    } /* }}} */
1792
1793    /**
1794     * Returns the greatest access privilege for a given group
1795     *
1796     * This method searches the access control list for entries of
1797     * group $group. If it finds more than one entry it will return the
1798     * one allowing the greatest privileges. If there is no entry in the
1799     * access control list, it will return the default access mode.
1800     * The function takes inherited access rights into account.
1801     * For a list of possible access rights see @file inc.AccessUtils.php
1802     *
1803     * @param SeedDMS_Core_Group $group object instance of class SeedDMS_Core_Group
1804     * @return integer access mode
1805     */
1806    public function getGroupAccessMode($group) { /* {{{ */
1807        $highestPrivileged = M_NONE;
1808
1809        //ACLs durchforsten
1810        $foundInACL = false;
1811        $accessList = $this->getAccessList();
1812        if (!$accessList)
1813            return false;
1814
1815        /** @var SeedDMS_Core_GroupAccess $groupAccess */
1816        foreach ($accessList["groups"] as $groupAccess) {
1817            if ($groupAccess->getGroupID() == $group->getID()) {
1818                $foundInACL = true;
1819                if ($groupAccess->getMode() > $highestPrivileged)
1820                    $highestPrivileged = $groupAccess->getMode();
1821                if ($highestPrivileged == M_ALL) // max access right -> skip the rest
1822                    return $highestPrivileged;
1823            }
1824        }
1825
1826        if ($foundInACL)
1827            return $highestPrivileged;
1828
1829        //Standard-Berechtigung verwenden
1830        return $this->getDefaultAccess();
1831    } /* }}} */
1832
1833    /**
1834     * Returns a list of all notifications
1835     *
1836     * The returned list has two elements called 'users' and 'groups'. Each one
1837     * is an array itself countaining objects of class SeedDMS_Core_User and
1838     * SeedDMS_Core_Group.
1839     *
1840     * @param integer $type type of notification (not yet used)
1841     * @param bool $incdisabled set to true if disabled user shall be included
1842     * @return array|bool
1843     */
1844    public function getNotifyList($type=0, $incdisabled=false) { /* {{{ */
1845        if (empty($this->_notifyList)) {
1846            $db = $this->_dms->getDB();
1847
1848            $queryStr ="SELECT * FROM `tblNotify` WHERE `targetType` = " . T_DOCUMENT . " AND `target` = " . $this->_id;
1849            $resArr = $db->getResultArray($queryStr);
1850            if (is_bool($resArr) && $resArr == false)
1851                return false;
1852
1853            $this->_notifyList = array("groups" => array(), "users" => array());
1854            foreach ($resArr as $row)
1855            {
1856                if ($row["userID"] != -1) {
1857                    $u = $this->_dms->getUser($row["userID"]);
1858                    if($u && (!$u->isDisabled() || $incdisabled))
1859                        array_push($this->_notifyList["users"], $u);
1860                } else { //if ($row["groupID"] != -1)
1861                    $g = $this->_dms->getGroup($row["groupID"]);
1862                    if($g)
1863                        array_push($this->_notifyList["groups"], $g);
1864                }
1865            }
1866        }
1867        return $this->_notifyList;
1868    } /* }}} */
1869
1870    /**
1871     * Make sure only users/groups with read access are in the notify list
1872     *
1873     */
1874    public function cleanNotifyList() { /* {{{ */
1875        // If any of the notification subscribers no longer have read access,
1876        // remove their subscription.
1877        if (empty($this->_notifyList))
1878            $this->getNotifyList();
1879
1880        /* Make a copy of both notifier lists because removeNotify will empty
1881         * $this->_notifyList and the second foreach will not work anymore.
1882         */
1883        /** @var SeedDMS_Core_User[] $nusers */
1884        $nusers = $this->_notifyList["users"];
1885        /** @var SeedDMS_Core_Group[] $ngroups */
1886        $ngroups = $this->_notifyList["groups"];
1887        foreach ($nusers as $u) {
1888            if ($this->getAccessMode($u) < M_READ) {
1889                $this->removeNotify($u->getID(), true);
1890            }
1891        }
1892        foreach ($ngroups as $g) {
1893            if ($this->getGroupAccessMode($g) < M_READ) {
1894                $this->removeNotify($g->getID(), false);
1895            }
1896        }
1897    } /* }}} */
1898
1899    /**
1900     * Add a user/group to the notification list
1901     *
1902     * This method does not check if the currently logged in user
1903     * is allowed to add a notification. This must be checked by the calling
1904     * application.
1905     *
1906     * @param $userOrGroupID integer id of user or group to add
1907     * @param $isUser integer 1 if $userOrGroupID is a user,
1908     *                0 if $userOrGroupID is a group
1909     * @return integer  0: Update successful.
1910     *                 -1: Invalid User/Group ID.
1911     *                 -2: Target User / Group does not have read access.
1912     *                 -3: User is already subscribed.
1913     *                 -4: Database / internal error.
1914     */
1915    public function addNotify($userOrGroupID, $isUser) { /* {{{ */
1916        $db = $this->_dms->getDB();
1917
1918        $userOrGroup = ($isUser ? "`userID`" : "`groupID`");
1919
1920        /* Verify that user / group exists. */
1921        $obj = ($isUser ? $this->_dms->getUser($userOrGroupID) : $this->_dms->getGroup($userOrGroupID));
1922        if (!is_object($obj)) {
1923            return -1;
1924        }
1925
1926        /* Verify that the requesting user has permission to add the target to
1927         * the notification system.
1928         */
1929        /*
1930         * The calling application should enforce the policy on who is allowed
1931         * to add someone to the notification system. If is shall remain here
1932         * the currently logged in user should be passed to this function
1933         *
1934        GLOBAL $user;
1935        if ($user->isGuest()) {
1936            return -2;
1937        }
1938        if (!$user->isAdmin()) {
1939            if ($isUser) {
1940                if ($user->getID() != $obj->getID()) {
1941                    return -2;
1942                }
1943            }
1944            else {
1945                if (!$obj->isMember($user)) {
1946                    return -2;
1947                }
1948            }
1949        }
1950         */
1951
1952        /* Verify that target user / group has read access to the document. */
1953        if ($isUser) {
1954            // Users are straightforward to check.
1955            if ($this->getAccessMode($obj) < M_READ) {
1956                return -2;
1957            }
1958        }
1959        else {
1960            // Groups are a little more complex.
1961            if ($this->getDefaultAccess() >= M_READ) {
1962                // If the default access is at least READ-ONLY, then just make sure
1963                // that the current group has not been explicitly excluded.
1964                $acl = $this->getAccessList(M_NONE, O_EQ);
1965                $found = false;
1966                /** @var SeedDMS_Core_GroupAccess $group */
1967                foreach ($acl["groups"] as $group) {
1968                    if ($group->getGroupID() == $userOrGroupID) {
1969                        $found = true;
1970                        break;
1971                    }
1972                }
1973                if ($found) {
1974                    return -2;
1975                }
1976            }
1977            else {
1978                // The default access is restricted. Make sure that the group has
1979                // been explicitly allocated access to the document.
1980                $acl = $this->getAccessList(M_READ, O_GTEQ);
1981                if (is_bool($acl)) {
1982                    return -4;
1983                }
1984                $found = false;
1985                /** @var SeedDMS_Core_GroupAccess $group */
1986                foreach ($acl["groups"] as $group) {
1987                    if ($group->getGroupID() == $userOrGroupID) {
1988                        $found = true;
1989                        break;
1990                    }
1991                }
1992                if (!$found) {
1993                    return -2;
1994                }
1995            }
1996        }
1997        /* Check to see if user/group is already on the list. */
1998        $queryStr = "SELECT * FROM `tblNotify` WHERE `tblNotify`.`target` = '".$this->_id."' ".
1999            "AND `tblNotify`.`targetType` = '".T_DOCUMENT."' ".
2000            "AND `tblNotify`.".$userOrGroup." = '".(int) $userOrGroupID."'";
2001        $resArr = $db->getResultArray($queryStr);
2002        if (is_bool($resArr)) {
2003            return -4;
2004        }
2005        if (count($resArr)>0) {
2006            return -3;
2007        }
2008
2009        $queryStr = "INSERT INTO `tblNotify` (`target`, `targetType`, " . $userOrGroup . ") VALUES (" . $this->_id . ", " . T_DOCUMENT . ", " . (int) $userOrGroupID . ")";
2010        if (!$db->getResult($queryStr))
2011            return -4;
2012
2013        unset($this->_notifyList);
2014        return 0;
2015    } /* }}} */
2016
2017    /**
2018     * Remove a user or group from the notification list
2019     *
2020     * This method does not check if the currently logged in user
2021     * is allowed to remove a notification. This must be checked by the calling
2022     * application.
2023     *
2024     * @param integer $userOrGroupID id of user or group
2025     * @param boolean $isUser boolean true if a user is passed in $userOrGroupID, false
2026     *        if a group is passed in $userOrGroupID
2027     * @param integer $type type of notification (0 will delete all) Not used yet!
2028     * @return integer 0 if operation was succesful
2029     *                 -1 if the userid/groupid is invalid
2030     *                 -3 if the user/group is already subscribed
2031     *                 -4 in case of an internal database error
2032     */
2033    public function removeNotify($userOrGroupID, $isUser, $type=0) { /* {{{ */
2034        $db = $this->_dms->getDB();
2035
2036        /* Verify that user / group exists. */
2037        /** @var SeedDMS_Core_Group|SeedDMS_Core_User $obj */
2038        $obj = ($isUser ? $this->_dms->getUser($userOrGroupID) : $this->_dms->getGroup($userOrGroupID));
2039        if (!is_object($obj)) {
2040            return -1;
2041        }
2042
2043        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
2044
2045        /* Verify that the requesting user has permission to add the target to
2046         * the notification system.
2047         */
2048        /*
2049         * The calling application should enforce the policy on who is allowed
2050         * to add someone to the notification system. If is shall remain here
2051         * the currently logged in user should be passed to this function
2052         *
2053        GLOBAL $user;
2054        if ($user->isGuest()) {
2055            return -2;
2056        }
2057        if (!$user->isAdmin()) {
2058            if ($isUser) {
2059                if ($user->getID() != $obj->getID()) {
2060                    return -2;
2061                }
2062            }
2063            else {
2064                if (!$obj->isMember($user)) {
2065                    return -2;
2066                }
2067            }
2068        }
2069         */
2070
2071        /* Check to see if the target is in the database. */
2072        $queryStr = "SELECT * FROM `tblNotify` WHERE `tblNotify`.`target` = '".$this->_id."' ".
2073            "AND `tblNotify`.`targetType` = '".T_DOCUMENT."' ".
2074            "AND `tblNotify`.".$userOrGroup." = '".(int) $userOrGroupID."'";
2075        $resArr = $db->getResultArray($queryStr);
2076        if (is_bool($resArr)) {
2077            return -4;
2078        }
2079        if (count($resArr)==0) {
2080            return -3;
2081        }
2082
2083        $queryStr = "DELETE FROM `tblNotify` WHERE `target` = " . $this->_id . " AND `targetType` = " . T_DOCUMENT . " AND " . $userOrGroup . " = " . (int) $userOrGroupID;
2084        /* If type is given then delete only those notifications */
2085        if($type)
2086            $queryStr .= " AND `type` = ".(int) $type;
2087        if (!$db->getResult($queryStr))
2088            return -4;
2089
2090        unset($this->_notifyList);
2091        return 0;
2092    } /* }}} */
2093
2094    /**
2095     * Add content to a document
2096     *
2097     * Each document may have any number of content elements attached to it.
2098     * Each content element has a version number. Newer versions (greater
2099     * version number) replace older versions.
2100     *
2101     * @param string $comment comment
2102     * @param object $user user who shall be the owner of this content
2103     * @param string $tmpFile file containing the actuall content
2104     * @param string $orgFileName original file name
2105     * @param string $fileType
2106     * @param string $mimeType MimeType of the content
2107     * @param array $reviewers list of reviewers
2108     * @param array $approvers list of approvers
2109     * @param integer $version version number of content or 0 if next higher version shall be used.
2110     * @param array $attributes list of version attributes. The element key
2111     *        must be the id of the attribute definition.
2112     * @param object $workflow
2113     * @param integer $initstate intial document status
2114     * @return bool|SeedDMS_Core_AddContentResultSet
2115     */
2116    function addContent($comment, $user, $tmpFile, $orgFileName, $fileType, $mimeType, $reviewers=array(), $approvers=array(), $version=0, $attributes=array(), $workflow=null, $initstate=S_RELEASED) { /* {{{ */
2117        $db = $this->_dms->getDB();
2118
2119        // the doc path is id/version.filetype
2120        $dir = $this->getDir();
2121
2122        /* The version field in table tblDocumentContent used to be auto
2123         * increment but that requires the field to be primary as well if
2124         * innodb is used. That's why the version is now determined here.
2125         */
2126        if ((int)$version<1) {
2127            $queryStr = "SELECT MAX(`version`) AS m FROM `tblDocumentContent` WHERE `document` = ".$this->_id;
2128            $resArr = $db->getResultArray($queryStr);
2129            if (is_bool($resArr) && !$resArr)
2130                return false;
2131
2132            $version = $resArr[0]['m']+1;
2133        }
2134
2135        if($fileType == '.')
2136            $fileType = '';
2137        $filesize = SeedDMS_Core_File::fileSize($tmpFile);
2138        $checksum = SeedDMS_Core_File::checksum($tmpFile);
2139
2140        $db->startTransaction();
2141        $queryStr = "INSERT INTO `tblDocumentContent` (`document`, `version`, `comment`, `date`, `createdBy`, `dir`, `orgFileName`, `fileType`, `mimeType`, `fileSize`, `checksum`) VALUES ".
2142                        "(".$this->_id.", ".(int)$version.",".$db->qstr($comment).", ".$db->getCurrentTimestamp().", ".$user->getID().", ".$db->qstr($dir).", ".$db->qstr($orgFileName).", ".$db->qstr($fileType).", ".$db->qstr($mimeType).", ".$filesize.", ".$db->qstr($checksum).")";
2143        if (!$db->getResult($queryStr)) {
2144            $db->rollbackTransaction();
2145            return false;
2146        }
2147
2148        $contentID = $db->getInsertID('tblDocumentContent');
2149
2150        // copy file
2151        if (!SeedDMS_Core_File::makeDir($this->_dms->contentDir . $dir)) {
2152            $db->rollbackTransaction();
2153            return false;
2154        }
2155        if($this->_dms->forceRename)
2156            $err = SeedDMS_Core_File::renameFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType);
2157        elseif($this->_dms->forceLink)
2158            $err = SeedDMS_Core_File::linkFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType);
2159        else
2160            $err = SeedDMS_Core_File::copyFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType);
2161        if (!$err) {
2162            $db->rollbackTransaction();
2163            return false;
2164        }
2165
2166        $this->_content = null;
2167        $this->_latestContent = null;
2168        $content = $this->getLatestContent($contentID); /** @todo: Parameter not defined in Funktion */
2169        $docResultSet = new SeedDMS_Core_AddContentResultSet($content);
2170        $docResultSet->setDMS($this->_dms);
2171
2172        if($attributes) {
2173            foreach($attributes as $attrdefid=>$attribute) {
2174                /* $attribute can be a string or an array */
2175                if($attribute) {
2176                    if($attrdef = $this->_dms->getAttributeDefinition($attrdefid)) {
2177                        if(!$content->setAttributeValue($attrdef, $attribute)) {
2178                            $this->_removeContent($content);
2179                            $db->rollbackTransaction();
2180                            return false;
2181                        }
2182                    } else {
2183                        $this->_removeContent($content);
2184                        $db->rollbackTransaction();
2185                        return false;
2186                    }
2187                }
2188            }
2189        }
2190
2191        $queryStr = "INSERT INTO `tblDocumentStatus` (`documentID`, `version`) ".
2192            "VALUES (". $this->_id .", ". (int) $version .")";
2193        if (!$db->getResult($queryStr)) {
2194            $this->_removeContent($content);
2195            $db->rollbackTransaction();
2196            return false;
2197        }
2198
2199        $statusID = $db->getInsertID('tblDocumentStatus', 'statusID');
2200
2201        if($workflow)
2202            $content->setWorkflow($workflow, $user);
2203
2204        // Add reviewers into the database. Reviewers must review the document
2205        // and submit comments, if appropriate. Reviewers can also recommend that
2206        // a document be rejected.
2207        $pendingReview=false;
2208        /** @noinspection PhpUnusedLocalVariableInspection */
2209        foreach (array("i", "g") as $i){
2210            if (isset($reviewers[$i])) {
2211                foreach ($reviewers[$i] as $reviewerID) {
2212                    $reviewer=($i=="i" ?$this->_dms->getUser($reviewerID) : $this->_dms->getGroup($reviewerID));
2213                    $res = ($i=="i" ? $docResultSet->getContent()->addIndReviewer($reviewer, $user, true) : $docResultSet->getContent()->addGrpReviewer($reviewer, $user, true));
2214                    $docResultSet->addReviewer($reviewer, $i, $res);
2215                    // If no error is returned, or if the error is just due to email
2216                    // failure, mark the state as "pending review".
2217                    // FIXME: There seems to be no error code -4 anymore
2218                    if ($res==0 || $res=-3 || $res=-4) {
2219                        $pendingReview=true;
2220                    }
2221                }
2222            }
2223        }
2224        // Add approvers to the database. Approvers must also review the document
2225        // and make a recommendation on its release as an approved version.
2226        $pendingApproval=false;
2227        /** @noinspection PhpUnusedLocalVariableInspection */
2228        foreach (array("i", "g") as $i){
2229            if (isset($approvers[$i])) {
2230                foreach ($approvers[$i] as $approverID) {
2231                    $approver=($i=="i" ? $this->_dms->getUser($approverID) : $this->_dms->getGroup($approverID));
2232                    $res=($i=="i" ? $docResultSet->getContent()->addIndApprover($approver, $user, true) : $docResultSet->getContent()->addGrpApprover($approver, $user, !$pendingReview));
2233                    $docResultSet->addApprover($approver, $i, $res);
2234                    // FIXME: There seems to be no error code -4 anymore
2235                    if ($res==0 || $res=-3 || $res=-4) {
2236                        $pendingApproval=true;
2237                    }
2238                }
2239            }
2240        }
2241
2242        // If there are no reviewers or approvers, the document is automatically
2243        // promoted to the released state.
2244        if ($pendingReview) {
2245            $status = S_DRAFT_REV;
2246            $comment = "";
2247        }
2248        elseif ($pendingApproval) {
2249            $status = S_DRAFT_APP;
2250            $comment = "";
2251        }
2252        elseif($workflow) {
2253            $status = S_IN_WORKFLOW;
2254            $comment = ", workflow: ".$workflow->getName();
2255        } elseif($initstate == S_DRAFT) {
2256            $status = $initstate;
2257            $comment = "";
2258        } else {
2259            $status = S_RELEASED;
2260            $comment = "";
2261        }
2262        $queryStr = "INSERT INTO `tblDocumentStatusLog` (`statusID`, `status`, `comment`, `date`, `userID`) ".
2263            "VALUES ('". $statusID ."', '". $status."', 'New document content submitted". $comment ."', ".$db->getCurrentDatetime().", '". $user->getID() ."')";
2264        if (!$db->getResult($queryStr)) {
2265            $db->rollbackTransaction();
2266            return false;
2267        }
2268
2269        /** @noinspection PhpMethodParametersCountMismatchInspection */
2270        $docResultSet->setStatus($status);
2271
2272        $db->commitTransaction();
2273        return $docResultSet;
2274    } /* }}} */
2275
2276    /**
2277     * Replace a version of a document
2278     *
2279     * Each document may have any number of content elements attached to it.
2280     * This method replaces the file content of a given version.
2281     * Using this function is highly discourage, because it undermines the
2282     * idea of keeping all versions of a document as originally saved.
2283     * Content will only be replaced if the mimetype, filetype, user and
2284     * original filename are identical to the version being updated.
2285     *
2286     * This method was introduced for the webdav server because any saving
2287     * of a document created a new version.
2288     *
2289     * @param object $user user who shall be the owner of this content
2290     * @param string $tmpFile file containing the actuall content
2291     * @param string $orgFileName original file name
2292     * @param string $fileType
2293     * @param string $mimeType MimeType of the content
2294     * @param integer $version version number of content or 0 if latest version shall be replaced.
2295     * @return bool/array false in case of an error or a result set
2296     */
2297    public function replaceContent($version, $user, $tmpFile, $orgFileName, $fileType, $mimeType, $allowoverride=[]) { /* {{{ */
2298        $db = $this->_dms->getDB();
2299
2300        // the doc path is id/version.filetype
2301        $dir = $this->getDir();
2302
2303        /* If $version < 1 than replace the content of the latest version.
2304         */
2305        if ((int) $version<1) {
2306            $queryStr = "SELECT MAX(`version`) AS m FROM `tblDocumentContent` WHERE `document` = ".$this->_id;
2307            $resArr = $db->getResultArray($queryStr);
2308            if (is_bool($resArr) && !$resArr)
2309                return false;
2310
2311            $version = $resArr[0]['m'];
2312        }
2313
2314        $content = $this->getContentByVersion($version);
2315        if(!$content)
2316            return false;
2317
2318        if($fileType == '.')
2319            $fileType = '';
2320
2321        $sql = [];
2322        /* Check if $user, $orgFileName, $fileType and $mimeType are the same */
2323        if($user->getID() != $content->getUser()->getID()) {
2324            if(!empty($allowoverride['user']))
2325                $sql[] = "`createdBy`=".$user->getID();
2326            else
2327                return false;
2328        }
2329        if($orgFileName != $content->getOriginalFileName()) {
2330            if(!empty($allowoverride['orgfilename']))
2331                $sql[] = "`orgFileName`=".$db->qstr($orgFileName);
2332            else
2333                return false;
2334        }
2335        if($fileType != $content->getFileType()) {
2336            if(!empty($allowoverride['filetype']))
2337                $sql[] = "`fileType`=".$db->qstr($fileType);
2338            else
2339                return false;
2340        }
2341        if($mimeType != $content->getMimeType()) {
2342            if(!empty($allowoverride['mimetype']))
2343                $sql[] = "`mimeType`=".$db->qstr($mimeType);
2344            else
2345                return false;
2346        }
2347
2348        $filesize = SeedDMS_Core_File::fileSize($tmpFile);
2349        $checksum = SeedDMS_Core_File::checksum($tmpFile);
2350
2351        $db->startTransaction();
2352        $sql[] = "`date`=".$db->getCurrentTimestamp();
2353        $sql[] = "`fileSize`=".$filesize;
2354        $sql[] = "`checksum`=".$db->qstr($checksum);
2355        $queryStr = "UPDATE `tblDocumentContent` set ".implode(", ", $sql)." WHERE `id`=".$content->getID();
2356        if (!$db->getResult($queryStr)) {
2357            $db->rollbackTransaction();
2358            return false;
2359        }
2360
2361        // copy file
2362        if (!SeedDMS_Core_File::copyFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType)) {
2363            $db->rollbackTransaction();
2364            return false;
2365        }
2366
2367        $this->_content = null;
2368        $this->_latestContent = null;
2369        $db->commitTransaction();
2370
2371        return true;
2372    } /* }}} */
2373
2374    /**
2375     * Return all content elements of a document
2376     *
2377     * This method returns an array of content elements ordered by version.
2378     * Version which are not accessible because of its status, will be filtered
2379     * out. Access rights based on the document status are calculated for the
2380     * currently logged in user.
2381     *
2382     * @return bool|SeedDMS_Core_DocumentContent[]
2383     */
2384    public function getContent() { /* {{{ */
2385        $db = $this->_dms->getDB();
2386
2387        if (!isset($this->_content)) {
2388            $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." ORDER BY `version`";
2389            $resArr = $db->getResultArray($queryStr);
2390            if (is_bool($resArr) && !$resArr)
2391                return false;
2392
2393            $this->_content = array();
2394            $classname = $this->_dms->getClassname('documentcontent');
2395            $user = $this->_dms->getLoggedInUser();
2396            foreach ($resArr as $row) {
2397                /** @var SeedDMS_Core_DocumentContent $content */
2398                $content = new $classname($row["id"], $this, $row["version"], $row["comment"], $row["date"], $row["createdBy"], $row["dir"], $row["orgFileName"], $row["fileType"], $row["mimeType"], $row['fileSize'], $row['checksum'], $row['revisiondate']);
2399                /* TODO: Better use content id as key in $this->_content. This
2400                 * would allow to remove a single content object in removeContent().
2401                 * Currently removeContent() must clear $this->_content completely
2402                 */
2403                if($user) {
2404                    if($content->getAccessMode($user) >= M_READ)
2405                        array_push($this->_content, $content);
2406                } else {
2407                    array_push($this->_content, $content);
2408                }
2409            }
2410        }
2411
2412        return $this->_content;
2413    } /* }}} */
2414
2415    /**
2416     * Return the content element of a document with a given version number
2417     *
2418     * This method will check if the version is accessible and return false
2419     * if not. Access rights based on the document status are calculated for the
2420     * currently logged in user.
2421     *
2422     * @param integer $version version number of content element
2423     * @return SeedDMS_Core_DocumentContent|null|boolean object of class
2424     * {@see SeedDMS_Core_DocumentContent}, null if not content was found,
2425     * false in case of an error
2426     */
2427    public function getContentByVersion($version) { /* {{{ */
2428        if (!is_numeric($version)) return false;
2429
2430        if (isset($this->_content)) {
2431            foreach ($this->_content as $revision) {
2432                if ($revision->getVersion() == $version)
2433                    return $revision;
2434            }
2435            return null;
2436        }
2437
2438        $db = $this->_dms->getDB();
2439        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." AND `version` = " . (int) $version;
2440        $resArr = $db->getResultArray($queryStr);
2441        if (is_bool($resArr) && !$resArr)
2442            return false;
2443        if (count($resArr) != 1)
2444            return null;
2445
2446        $resArr = $resArr[0];
2447        $classname = $this->_dms->getClassname('documentcontent');
2448        /** @var SeedDMS_Core_DocumentContent $content */
2449        if($content = new $classname($resArr["id"], $this, $resArr["version"], $resArr["comment"], $resArr["date"], $resArr["createdBy"], $resArr["dir"], $resArr["orgFileName"], $resArr["fileType"], $resArr["mimeType"], $resArr['fileSize'], $resArr['checksum'], $resArr['revisiondate'])) {
2450            $user = $this->_dms->getLoggedInUser();
2451            /* A user with write access on the document may always see the version */
2452            if($user && $content->getAccessMode($user) == M_NONE)
2453                return null;
2454            else
2455                return $content;
2456        } else {
2457            return false;
2458        }
2459    } /* }}} */
2460
2461    /**
2462     * Check if a given version is the latest version of the document
2463     *
2464     * @param integer $version version number of content element
2465     * @return SeedDMS_Core_DocumentContent|boolean object of class {@see SeedDMS_Core_DocumentContent}
2466     * or false
2467     */
2468    public function isLatestContent($version) { /* {{{ */
2469        return $this->getLatestContent()->getVersion() == $version;
2470    } /* }}} */
2471
2472    /**
2473     * @return bool|null|SeedDMS_Core_DocumentContent
2474     */
2475    private function __getLatestContent() { /* {{{ */
2476        if (!$this->_latestContent) {
2477            $db = $this->_dms->getDB();
2478            $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." ORDER BY `version` DESC LIMIT 1";
2479            $resArr = $db->getResultArray($queryStr);
2480            if (is_bool($resArr) && !$resArr)
2481                return false;
2482            if (count($resArr) != 1)
2483                return false;
2484
2485            $resArr = $resArr[0];
2486            $classname = $this->_dms->getClassname('documentcontent');
2487            $this->_latestContent = new $classname($resArr["id"], $this, $resArr["version"], $resArr["comment"], $resArr["date"], $resArr["createdBy"], $resArr["dir"], $resArr["orgFileName"], $resArr["fileType"], $resArr["mimeType"], $resArr['fileSize'], $resArr['checksum'], $resArr['revisiondate']);
2488        }
2489        return $this->_latestContent;
2490    } /* }}} */
2491
2492    /**
2493     * Get the latest version of document
2494     *
2495     * This method returns the latest accessible version of a document.
2496     * If content access has been restricted by setting
2497     * {@see SeedDMS_Core_DMS::noReadForStatus} the function will go
2498     * backwards in history until an accessible version is found. If none
2499     * is found null will be returned.
2500     * Access rights based on the document status are calculated for the
2501     * currently logged in user.
2502     *
2503     * @return bool|SeedDMS_Core_DocumentContent object of class {@see SeedDMS_Core_DocumentContent}
2504     */
2505    public function getLatestContent() { /* {{{ */
2506        if (!$this->_latestContent) {
2507            $db = $this->_dms->getDB();
2508            $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." ORDER BY `version` DESC";
2509            $resArr = $db->getResultArray($queryStr);
2510            if (is_bool($resArr) && !$resArr)
2511                return false;
2512
2513            $classname = $this->_dms->getClassname('documentcontent');
2514            $user = $this->_dms->getLoggedInUser();
2515            foreach ($resArr as $row) {
2516                /** @var SeedDMS_Core_DocumentContent $content */
2517                if (!$this->_latestContent) {
2518                    $content = new $classname($row["id"], $this, $row["version"], $row["comment"], $row["date"], $row["createdBy"], $row["dir"], $row["orgFileName"], $row["fileType"], $row["mimeType"], $row['fileSize'], $row['checksum'], $row['revisiondate']);
2519                    if($user) {
2520                        /* If the user may even write the document, then also allow to see all content.
2521                         * This is needed because the user could upload a new version
2522                         */
2523                        if($content->getAccessMode($user) >= M_READ) {
2524                            $this->_latestContent = $content;
2525                        }
2526                    } else {
2527                        $this->_latestContent = $content;
2528                    }
2529                }
2530            }
2531        }
2532
2533        return $this->_latestContent;
2534    } /* }}} */
2535
2536    /**
2537     * Remove version of document
2538     *
2539     * @param SeedDMS_Core_DocumentContent $version version number of content
2540     * @return boolean true if successful, otherwise false
2541     */
2542    private function _removeContent($version) { /* {{{ */
2543        $db = $this->_dms->getDB();
2544
2545        $db->startTransaction();
2546
2547        $status = $version->getStatus();
2548        $stID = $status["statusID"];
2549
2550        $queryStr = "DELETE FROM `tblDocumentContent` WHERE `document` = " . $this->getID() . " AND `version` = " . $version->getVersion();
2551        if (!$db->getResult($queryStr)) {
2552            $db->rollbackTransaction();
2553            return false;
2554        }
2555
2556        $queryStr = "DELETE FROM `tblDocumentContentAttributes` WHERE `content` = " . $version->getId();
2557        if (!$db->getResult($queryStr)) {
2558            $db->rollbackTransaction();
2559            return false;
2560        }
2561
2562        $queryStr = "DELETE FROM `tblTransmittalItems` WHERE `document` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2563        if (!$db->getResult($queryStr)) {
2564            $db->rollbackTransaction();
2565            return false;
2566        }
2567
2568        $queryStr = "DELETE FROM `tblDocumentStatusLog` WHERE `statusID` = '".$stID."'";
2569        if (!$db->getResult($queryStr)) {
2570            $db->rollbackTransaction();
2571            return false;
2572        }
2573
2574        $queryStr = "DELETE FROM `tblDocumentStatus` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2575        if (!$db->getResult($queryStr)) {
2576            $db->rollbackTransaction();
2577            return false;
2578        }
2579
2580        $status = $version->getReviewStatus();
2581        $stList = "";
2582        foreach ($status as $st) {
2583            $stList .= (strlen($stList)==0 ? "" : ", "). "'".$st["reviewID"]."'";
2584            $queryStr = "SELECT * FROM `tblDocumentReviewLog` WHERE `reviewID` = " . $st['reviewID'];
2585            $resArr = $db->getResultArray($queryStr);
2586            if ((is_bool($resArr) && !$resArr)) {
2587                $db->rollbackTransaction();
2588                return false;
2589            }
2590            foreach($resArr as $res) {
2591                $file = $this->_dms->contentDir . $this->getDir().'r'.$res['reviewLogID'];
2592                if(SeedDMS_Core_File::file_exists($file))
2593                    SeedDMS_Core_File::removeFile($file);
2594            }
2595        }
2596
2597        if (strlen($stList)>0) {
2598            $queryStr = "DELETE FROM `tblDocumentReviewLog` WHERE `tblDocumentReviewLog`.`reviewID` IN (".$stList.")";
2599            if (!$db->getResult($queryStr)) {
2600                $db->rollbackTransaction();
2601                return false;
2602            }
2603        }
2604        $queryStr = "DELETE FROM `tblDocumentReviewers` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2605        if (!$db->getResult($queryStr)) {
2606            $db->rollbackTransaction();
2607            return false;
2608        }
2609        $status = $version->getApprovalStatus();
2610        $stList = "";
2611        foreach ($status as $st) {
2612            $stList .= (strlen($stList)==0 ? "" : ", "). "'".$st["approveID"]."'";
2613            $queryStr = "SELECT * FROM `tblDocumentApproveLog` WHERE `approveID` = " . $st['approveID'];
2614            $resArr = $db->getResultArray($queryStr);
2615            if ((is_bool($resArr) && !$resArr)) {
2616                $db->rollbackTransaction();
2617                return false;
2618            }
2619            foreach($resArr as $res) {
2620                $file = $this->_dms->contentDir . $this->getDir().'a'.$res['approveLogID'];
2621                if(SeedDMS_Core_File::file_exists($file))
2622                    SeedDMS_Core_File::removeFile($file);
2623            }
2624        }
2625
2626        if (strlen($stList)>0) {
2627            $queryStr = "DELETE FROM `tblDocumentApproveLog` WHERE `tblDocumentApproveLog`.`approveID` IN (".$stList.")";
2628            if (!$db->getResult($queryStr)) {
2629                $db->rollbackTransaction();
2630                return false;
2631            }
2632        }
2633        $queryStr = "DELETE FROM `tblDocumentApprovers` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2634        if (!$db->getResult($queryStr)) {
2635            $db->rollbackTransaction();
2636            return false;
2637        }
2638
2639        /* Remove all receipts of document version.
2640         * This implmentation is different from the above for removing approvals
2641         * and reviews. It doesn't use getReceiptStatus() but reads the database
2642         */
2643        $queryStr = "SELECT * FROM `tblDocumentRecipients` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2644        $resArr = $db->getResultArray($queryStr);
2645        if ((is_bool($resArr) && !$resArr)) {
2646            $db->rollbackTransaction();
2647            return false;
2648        }
2649
2650        $stList = array();
2651        foreach($resArr as $res) {
2652            $stList[] = $res['receiptID'];
2653        }
2654
2655        if ($stList) {
2656            $queryStr = "DELETE FROM `tblDocumentReceiptLog` WHERE `receiptID` IN (".implode(',', $stList).")";
2657            if (!$db->getResult($queryStr)) {
2658                $db->rollbackTransaction();
2659                return false;
2660            }
2661            $queryStr = "DELETE FROM `tblDocumentRecipients` WHERE `receiptID` IN (".implode(',', $stList).")";
2662            if (!$db->getResult($queryStr)) {
2663                $db->rollbackTransaction();
2664                return false;
2665            }
2666        }
2667
2668        /* Remove all revisions of document version.
2669         * This implementation is different from the above for removing approvals
2670         * and reviews. It doesn't use getRevisionStatus() but reads the database
2671         */
2672        $queryStr = "SELECT * FROM `tblDocumentRevisors` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2673        $resArr = $db->getResultArray($queryStr);
2674        if ((is_bool($resArr) && !$resArr)) {
2675            $db->rollbackTransaction();
2676            return false;
2677        }
2678
2679        $stList = array();
2680        foreach($resArr as $res) {
2681            $stList[] = $res['revisionID'];
2682        }
2683
2684        if ($stList) {
2685            $queryStr = "DELETE FROM `tblDocumentRevisionLog` WHERE `revisionID` IN (".implode(',', $stList).")";
2686            if (!$db->getResult($queryStr)) {
2687                $db->rollbackTransaction();
2688                return false;
2689            }
2690            $queryStr = "DELETE FROM `tblDocumentRevisors` WHERE `revisionID` IN (".implode(',', $stList).")";
2691            if (!$db->getResult($queryStr)) {
2692                $db->rollbackTransaction();
2693                return false;
2694            }
2695        }
2696
2697        $queryStr = "DELETE FROM `tblWorkflowDocumentContent` WHERE `document` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2698        if (!$db->getResult($queryStr)) {
2699            $db->rollbackTransaction();
2700            return false;
2701        }
2702
2703        /* Will be deleted automatically when record will be deleted
2704         * from tblWorkflowDocumentContent
2705        $queryStr = "DELETE FROM `tblWorkflowLog` WHERE `document` = '". $this->getID() ."' AND `version` = '" . $version->getVersion."'";
2706        if (!$db->getResult($queryStr)) {
2707            $db->rollbackTransaction();
2708            return false;
2709        }
2710         */
2711
2712        // remove only those document files attached to version
2713        $res = $this->getDocumentFiles($version->getVersion(), false);
2714        if (is_bool($res) && !$res) {
2715            $db->rollbackTransaction();
2716            return false;
2717        }
2718
2719        foreach ($res as $documentfile)
2720            if(!$this->removeDocumentFile($documentfile->getId())) {
2721                $db->rollbackTransaction();
2722                return false;
2723            }
2724
2725        if (SeedDMS_Core_File::file_exists( $this->_dms->contentDir.$version->getPath() ))
2726            if (!SeedDMS_Core_File::removeFile( $this->_dms->contentDir.$version->getPath() )) {
2727                $db->rollbackTransaction();
2728                return false;
2729            }
2730
2731        $db->commitTransaction();
2732        return true;
2733    } /* }}} */
2734
2735    /**
2736     * Call callback onPreRemoveDocument before deleting content
2737     *
2738     * @param SeedDMS_Core_DocumentContent $version version number of content
2739     * @return bool|mixed
2740     */
2741    public function removeContent($version) { /* {{{ */
2742        $this->_dms->lasterror = '';
2743        $db = $this->_dms->getDB();
2744
2745        /* Make sure the version exists */
2746        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = " . $this->getID() . " AND `version` = " . $version->getVersion();
2747        $resArr = $db->getResultArray($queryStr);
2748        if (is_bool($resArr) && !$resArr)
2749            return false;
2750        if (count($resArr)==0)
2751            return false;
2752
2753        /* Make sure this is not the last version */
2754        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = " . $this->getID();
2755        $resArr = $db->getResultArray($queryStr);
2756        if (is_bool($resArr) && !$resArr)
2757            return false;
2758        if (count($resArr)==1)
2759            return false;
2760
2761        /* Check if 'onPreRemoveDocument' callback is set */
2762        if(isset($this->_dms->callbacks['onPreRemoveContent'])) {
2763            foreach($this->_dms->callbacks['onPreRemoveContent'] as $callback) {
2764                $ret = call_user_func($callback[0], $callback[1], $this, $version);
2765                if(is_bool($ret))
2766                    return $ret;
2767            }
2768        }
2769
2770        if(false === ($ret = self::_removeContent($version))) {
2771            return false;
2772        }
2773
2774        /* Invalidate the content list and the latest content of this document,
2775         * otherwise getContent() and getLatestContent()
2776         * will still return the content just deleted.
2777         */
2778        $this->_latestContent = null;
2779        $this->_content = null;
2780
2781        /* Check if 'onPostRemoveDocument' callback is set */
2782        if(isset($this->_dms->callbacks['onPostRemoveContent'])) {
2783            foreach($this->_dms->callbacks['onPostRemoveContent'] as $callback) {
2784                if(!call_user_func($callback[0], $callback[1], $version)) {
2785                }
2786            }
2787        }
2788
2789        return $ret;
2790    } /* }}} */
2791
2792    /**
2793     * Return a certain document link
2794     *
2795     * @param integer $linkID id of link
2796     * @return SeedDMS_Core_DocumentLink|bool of SeedDMS_Core_DocumentLink or false in case of
2797     *         an error.
2798     */
2799    public function getDocumentLink($linkID) { /* {{{ */
2800        $db = $this->_dms->getDB();
2801
2802        if (!is_numeric($linkID)) return false;
2803
2804        $queryStr = "SELECT * FROM `tblDocumentLinks` WHERE `document` = " . $this->_id ." AND `id` = " . (int) $linkID;
2805        $resArr = $db->getResultArray($queryStr);
2806        if (is_bool($resArr) && !$resArr)
2807            return false;
2808        if (count($resArr)==0)
2809            return null;
2810
2811        $resArr = $resArr[0];
2812        $document = $this->_dms->getDocument($resArr["document"]);
2813        $target = $this->_dms->getDocument($resArr["target"]);
2814        if($document && $target) {
2815            $link = new SeedDMS_Core_DocumentLink($resArr["id"], $document, $target, $resArr["userID"], $resArr["public"]);
2816            $user = $this->_dms->getLoggedInUser();
2817            if($link->getAccessMode($user, $document, $target) >= M_READ)
2818                return $link;
2819        }
2820        return null;
2821    } /* }}} */
2822
2823    /**
2824     * Return all document links
2825     *
2826     * The list may contain all links to other documents, even those which
2827     * may not be visible by certain users, unless you pass appropriate
2828     * parameters to filter out public links and those created by
2829     * the given user. The two parameters are or'ed. If $publiconly
2830     * is set the method will return all public links disregarding the
2831     * user. If $publiconly is not set but a user is set, the method
2832     * will return all links of that user (public and none public).
2833     * Setting a user and $publiconly to true will *not* return the
2834     * public links of that user but all links which are public or
2835     * owned by that user.
2836     *
2837     * The application must call
2838     * SeedDMS_Core_DMS::filterDocumentLinks() afterwards to filter out
2839     * those links pointing to a document not accessible by a given user.
2840     *
2841     * @param boolean           $publiconly return all publically visible links
2842     * @param SeedDMS_Core_User $user       return also private links of this user
2843     *
2844     * @return array list of objects of class {@see SeedDMS_Core_DocumentLink}
2845     */
2846    public function getDocumentLinks($publiconly=false, $user=null) { /* {{{ */
2847        if (!isset($this->_documentLinks)) {
2848            $db = $this->_dms->getDB();
2849
2850            $queryStr = "SELECT * FROM `tblDocumentLinks` WHERE `document` = " . $this->_id;
2851            $tmp = array();
2852            if($publiconly)
2853                $tmp[] = "`public`=1";
2854            if($user)
2855                $tmp[] = "`userID`=".$user->getID();
2856            if($tmp) {
2857                $queryStr .= " AND (".implode(" OR ", $tmp).")";
2858            }
2859
2860            $resArr = $db->getResultArray($queryStr);
2861            if (is_bool($resArr) && !$resArr)
2862                return false;
2863            $this->_documentLinks = array();
2864
2865            $user = $this->_dms->getLoggedInUser();
2866            foreach ($resArr as $row) {
2867                $target = $this->_dms->getDocument($row["target"]);
2868                if($target) {
2869                    $link = new SeedDMS_Core_DocumentLink($row["id"], $this, $target, $row["userID"], $row["public"]);
2870                    if($link->getAccessMode($user, $this, $target) >= M_READ)
2871                        array_push($this->_documentLinks, $link);
2872                }
2873            }
2874        }
2875        return $this->_documentLinks;
2876    } /* }}} */
2877
2878    /**
2879     * Return all document having a link on this document
2880     *
2881     * The list contains all documents which have a link to the current
2882     * document. The list contains even those documents which
2883     * may not be accessible by the user, unless you pass appropriate
2884     * parameters to filter out public links and those created by
2885     * the given user.
2886     * This method is basically the reverse of
2887     * {@see SeedDMS_Core_Document::getDocumentLinks()}
2888     *
2889     * The application must call
2890     * SeedDMS_Core_DMS::filterDocumentLinks() afterwards to filter out
2891     * those links pointing to a document not accessible by a given user.
2892     *
2893     * @param boolean           $publiconly return all publically visible links
2894     * @param SeedDMS_Core_User $user       return also private links of this user
2895     *
2896     * @return array list of objects of class SeedDMS_Core_DocumentLink
2897     */
2898    public function getReverseDocumentLinks($publiconly=false, $user=null) { /* {{{ */
2899        $db = $this->_dms->getDB();
2900
2901        $queryStr = "SELECT * FROM `tblDocumentLinks` WHERE `target` = " . $this->_id;
2902        $tmp = array();
2903        if($publiconly)
2904            $tmp[] = "`public`=1";
2905        if($user)
2906            $tmp[] = "`userID`=".$user->getID();
2907        if($tmp) {
2908            $queryStr .= " AND (".implode(" OR ", $tmp).")";
2909        }
2910
2911        $resArr = $db->getResultArray($queryStr);
2912        if (is_bool($resArr) && !$resArr)
2913            return false;
2914
2915        $links = array();
2916        foreach ($resArr as $row) {
2917            $document = $this->_dms->getDocument($row["document"]);
2918            $link = new SeedDMS_Core_DocumentLink($row["id"], $document, $this, $row["userID"], $row["public"]);
2919            if($link->getAccessMode($user, $document, $this) >= M_READ)
2920                array_push($links, $link);
2921        }
2922
2923        return $links;
2924    } /* }}} */
2925
2926    /**
2927     * Add a link to a target document
2928     *
2929     * @param int $targetID Id of target document
2930     * @param int $userID Id of user adding the link
2931     * @param boolean true if link is public
2932     * @return SeedDMS_Core_DocumentLink|boolean
2933     */
2934    public function addDocumentLink($targetID, $userID, $public) { /* {{{ */
2935        $db = $this->_dms->getDB();
2936
2937        $public = ($public) ? 1 : 0;
2938
2939        if (!is_numeric($targetID) || $targetID < 1)
2940            return false;
2941
2942        if ($targetID == $this->_id)
2943            return false;
2944
2945        if (!is_numeric($userID) || $userID < 1)
2946            return false;
2947
2948        if(!($target = $this->_dms->getDocument($targetID)))
2949            return false;
2950
2951        if(!($user = $this->_dms->getUser($userID)))
2952            return false;
2953
2954        $queryStr = "INSERT INTO `tblDocumentLinks` (`document`, `target`, `userID`, `public`) VALUES (".$this->_id.", ".(int)$targetID.", ".(int)$userID.", ".$public.")";
2955        if (!$db->getResult($queryStr))
2956            return false;
2957
2958        unset($this->_documentLinks);
2959
2960        $id = $db->getInsertID('tblDocumentLinks');
2961        $link = new SeedDMS_Core_DocumentLink($id, $this, $target, $user->getId(), $public);
2962        return $link;
2963    } /* }}} */
2964
2965    public function removeDocumentLink($linkID) { /* {{{ */
2966        $db = $this->_dms->getDB();
2967
2968        if (!is_numeric($linkID) || $linkID < 1)
2969            return false;
2970
2971        $queryStr = "DELETE FROM `tblDocumentLinks` WHERE `document` = " . $this->_id ." AND `id` = " . (int) $linkID;
2972        if (!$db->getResult($queryStr)) return false;
2973        unset ($this->_documentLinks);
2974        return true;
2975    } /* }}} */
2976
2977    /**
2978     * Get attached file by its id
2979     *
2980     * @return object instance of SeedDMS_Core_DocumentFile, null if file is not
2981     * accessible, false in case of an sql error
2982     */
2983    public function getDocumentFile($ID) { /* {{{ */
2984        $db = $this->_dms->getDB();
2985
2986        if (!is_numeric($ID)) return false;
2987
2988        $queryStr = "SELECT * FROM `tblDocumentFiles` WHERE `document` = " . $this->_id ." AND `id` = " . (int) $ID;
2989        $resArr = $db->getResultArray($queryStr);
2990        if ((is_bool($resArr) && !$resArr) || count($resArr)==0) return false;
2991
2992        $resArr = $resArr[0];
2993        $classname = $this->_dms->getClassname('documentfile');
2994        $file = new $classname($resArr["id"], $this, $resArr["userID"], $resArr["comment"], $resArr["date"], $resArr["dir"], $resArr["fileType"], $resArr["mimeType"], $resArr["orgFileName"], $resArr["name"],$resArr["version"],$resArr["public"]);
2995        $user = $this->_dms->getLoggedInUser();
2996        if($file->getAccessMode($user) >= M_READ)
2997            return $file;
2998        return null;
2999    } /* }}} */
3000
3001    /**
3002     * Get list of files attached to document
3003     *
3004     * @param integer $version      get only attachments for this version
3005     * @param boolean $incnoversion include attachments without a version
3006     *
3007     * @return array list of files, false in case of an sql error
3008     */
3009    public function getDocumentFiles($version=0, $incnoversion=true) { /* {{{ */
3010        /* use a smarter caching because removing a document will call this function
3011         * for each version and the document itself.
3012         */
3013        $hash = substr(md5($version.$incnoversion), 0, 4);
3014        if (!isset($this->_documentFiles[$hash])) {
3015            $db = $this->_dms->getDB();
3016
3017            $queryStr = "SELECT * FROM `tblDocumentFiles` WHERE `document` = " . $this->_id;
3018            if($version) {
3019                if($incnoversion)
3020                    $queryStr .= " AND (`version`=0 OR `version`=".(int) $version.")";
3021                else
3022                    $queryStr .= " AND (`version`=".(int) $version.")";
3023            }
3024            $queryStr .= " ORDER BY ";
3025            if($version) {
3026                $queryStr .= "`version` DESC,";
3027            }
3028            $queryStr .= "`date` DESC";
3029            $resArr = $db->getResultArray($queryStr);
3030            if (is_bool($resArr) && !$resArr) return false;
3031
3032            $this->_documentFiles = array($hash=>array());
3033
3034            $user = $this->_dms->getLoggedInUser();
3035            $classname = $this->_dms->getClassname('documentfile');
3036            foreach ($resArr as $row) {
3037                $file = new $classname($row["id"], $this, $row["userID"], $row["comment"], $row["date"], $row["dir"], $row["fileType"], $row["mimeType"], $row["orgFileName"], $row["name"], $row["version"], $row["public"]);
3038                if($file->getAccessMode($user) >= M_READ)
3039                    array_push($this->_documentFiles[$hash], $file);
3040            }
3041        }
3042        return $this->_documentFiles[$hash];
3043    } /* }}} */
3044
3045    /**
3046     * Add an attachment to the document
3047     *
3048     */
3049    public function addDocumentFile($name, $comment, $user, $tmpFile, $orgFileName, $fileType, $mimeType, $version=0, $public=1) { /* {{{ */
3050        $db = $this->_dms->getDB();
3051
3052        $dir = $this->getDir();
3053
3054        $db->startTransaction();
3055        $queryStr = "INSERT INTO `tblDocumentFiles` (`comment`, `date`, `dir`, `document`, `fileType`, `mimeType`, `orgFileName`, `userID`, `name`, `version`, `public`) VALUES ".
3056            "(".$db->qstr($comment).", ".$db->getCurrentTimestamp().", ".$db->qstr($dir).", ".$this->_id.", ".$db->qstr($fileType).", ".$db->qstr($mimeType).", ".$db->qstr($orgFileName).",".$user->getID().",".$db->qstr($name).", ".((int) $version).", ".($public ? 1 : 0).")";
3057        if (!$db->getResult($queryStr)) {
3058            $db->rollbackTransaction();
3059            return false;
3060        }
3061
3062        $id = $db->getInsertID('tblDocumentFiles');
3063
3064        $file = $this->getDocumentFile($id);
3065        if (is_bool($file) && !$file) {
3066            $db->rollbackTransaction();
3067            return false;
3068        }
3069
3070        // copy file
3071        if (!SeedDMS_Core_File::makeDir($this->_dms->contentDir . $dir)) return false;
3072        if($this->_dms->forceRename)
3073            $err = SeedDMS_Core_File::renameFile($tmpFile, $this->_dms->contentDir . $file->getPath());
3074        else
3075            $err = SeedDMS_Core_File::copyFile($tmpFile, $this->_dms->contentDir . $file->getPath());
3076        if (!$err) {
3077            $db->rollbackTransaction();
3078            return false;
3079        }
3080
3081        $db->commitTransaction();
3082        unset ($this->_documentFiles);
3083        return $file;
3084    } /* }}} */
3085
3086    public function removeDocumentFile($ID) { /* {{{ */
3087        $db = $this->_dms->getDB();
3088
3089        if (!is_numeric($ID) || $ID < 1)
3090            return false;
3091
3092        $file = $this->getDocumentFile($ID);
3093        if (is_bool($file) && !$file) return false;
3094
3095        $db->startTransaction();
3096        /* First delete the database record, because that can be undone
3097         * if deletion of the file fails.
3098         */
3099        $queryStr = "DELETE FROM `tblDocumentFiles` WHERE `document` = " . $this->getID() . " AND `id` = " . (int) $ID;
3100        if (!$db->getResult($queryStr)) {
3101            $db->rollbackTransaction();
3102            return false;
3103        }
3104
3105        if (SeedDMS_Core_File::file_exists( $this->_dms->contentDir . $file->getPath() )){
3106            if (!SeedDMS_Core_File::removeFile( $this->_dms->contentDir . $file->getPath() )) {
3107                $db->rollbackTransaction();
3108                return false;
3109            }
3110        }
3111
3112        $db->commitTransaction();
3113        unset ($this->_documentFiles);
3114
3115        return true;
3116    } /* }}} */
3117
3118    /**
3119     * Remove a document completly
3120     *
3121     * This methods calls the callback 'onPreRemoveDocument' before removing
3122     * the document. The current document will be passed as the second
3123     * parameter to the callback function. After successful deletion the
3124     * 'onPostRemoveDocument' callback will be used. The current document id
3125     * will be passed as the second parameter. If onPreRemoveDocument fails
3126     * the whole function will fail and the document will not be deleted.
3127     * The return value of 'onPostRemoveDocument' will be disregarded.
3128     *
3129     * @return boolean true on success, otherwise false
3130     */
3131    public function remove() { /* {{{ */
3132        $db = $this->_dms->getDB();
3133        $this->_dms->lasterror = '';
3134
3135        /* Check if 'onPreRemoveDocument' callback is set */
3136        if(isset($this->_dms->callbacks['onPreRemoveDocument'])) {
3137            foreach($this->_dms->callbacks['onPreRemoveDocument'] as $callback) {
3138                $ret = call_user_func($callback[0], $callback[1], $this);
3139                if(is_bool($ret))
3140                    return $ret;
3141            }
3142        }
3143
3144        $res = $this->getContent();
3145        if (is_bool($res) && !$res) return false;
3146
3147        $db->startTransaction();
3148
3149        // remove content of document
3150        foreach ($this->_content as $version) {
3151            if (!$this->_removeContent($version)) {
3152                $db->rollbackTransaction();
3153                return false;
3154            }
3155        }
3156
3157        // remove all document files
3158        $res = $this->getDocumentFiles();
3159        if (is_bool($res) && !$res) {
3160            $db->rollbackTransaction();
3161            return false;
3162        }
3163
3164        foreach ($res as $documentfile)
3165            if(!$this->removeDocumentFile($documentfile->getId())) {
3166                $db->rollbackTransaction();
3167                return false;
3168            }
3169
3170        // TODO: versioning file?
3171
3172        if (SeedDMS_Core_File::file_exists( $this->_dms->contentDir . $this->getDir() ))
3173            if (!SeedDMS_Core_File::removeDir( $this->_dms->contentDir . $this->getDir() )) {
3174                $db->rollbackTransaction();
3175                return false;
3176            }
3177
3178        $queryStr = "DELETE FROM `tblDocuments` WHERE `id` = " . $this->_id;
3179        if (!$db->getResult($queryStr)) {
3180            $db->rollbackTransaction();
3181            return false;
3182        }
3183        $queryStr = "DELETE FROM `tblDocumentAttributes` WHERE `document` = " . $this->_id;
3184        if (!$db->getResult($queryStr)) {
3185            $db->rollbackTransaction();
3186            return false;
3187        }
3188        $queryStr = "DELETE FROM `tblACLs` WHERE `target` = " . $this->_id . " AND `targetType` = " . T_DOCUMENT;
3189        if (!$db->getResult($queryStr)) {
3190            $db->rollbackTransaction();
3191            return false;
3192        }
3193        $queryStr = "DELETE FROM `tblDocumentLinks` WHERE `document` = " . $this->_id . " OR `target` = " . $this->_id;
3194        if (!$db->getResult($queryStr)) {
3195            $db->rollbackTransaction();
3196            return false;
3197        }
3198        $queryStr = "DELETE FROM `tblDocumentLocks` WHERE `document` = " . $this->_id;
3199        if (!$db->getResult($queryStr)) {
3200            $db->rollbackTransaction();
3201            return false;
3202        }
3203        $queryStr = "DELETE FROM `tblDocumentCheckOuts` WHERE `document` = " . $this->_id;
3204        if (!$db->getResult($queryStr)) {
3205            $db->rollbackTransaction();
3206            return false;
3207        }
3208        $queryStr = "DELETE FROM `tblDocumentFiles` WHERE `document` = " . $this->_id;
3209        if (!$db->getResult($queryStr)) {
3210            $db->rollbackTransaction();
3211            return false;
3212        }
3213        $queryStr = "DELETE FROM `tblDocumentCategory` WHERE `documentID` = " . $this->_id;
3214        if (!$db->getResult($queryStr)) {
3215            $db->rollbackTransaction();
3216            return false;
3217        }
3218
3219        // Delete the notification list.
3220        $queryStr = "DELETE FROM `tblNotify` WHERE `target` = " . $this->_id . " AND `targetType` = " . T_DOCUMENT;
3221        if (!$db->getResult($queryStr)) {
3222            $db->rollbackTransaction();
3223            return false;
3224        }
3225
3226        $db->commitTransaction();
3227
3228        /* Check if 'onPostRemoveDocument' callback is set */
3229        if(isset($this->_dms->callbacks['onPostRemoveDocument'])) {
3230            foreach($this->_dms->callbacks['onPostRemoveDocument'] as $callback) {
3231                if(!call_user_func($callback[0], $callback[1], $this)) {
3232                }
3233            }
3234        }
3235
3236        return true;
3237    } /* }}} */
3238
3239    /**
3240     * Get List of users and groups which have read access on the document.
3241     * The list will not include any guest users,
3242     * administrators and the owner of the document.
3243     *
3244     * This method is deprecated. Use
3245     * {@see SeedDMS_Core_Document::getReadAccessList()} instead.
3246     */
3247    protected function __getApproversList() { /* {{{ */
3248        return $this->getReadAccessList(0, 0, 0);
3249    } /* }}} */
3250
3251    /**
3252     * Returns a list of groups and users with read access on the document
3253     *
3254     * @param boolean $listadmin if set to true any admin will be listed too
3255     * @param boolean $listowner if set to true the owner will be listed too
3256     * @param boolean $listguest if set to true any guest will be listed too
3257     *
3258     * @return array list of users and groups
3259     */
3260    public function getReadAccessList($listadmin=0, $listowner=0, $listguest=0) { /* {{{ */
3261        $db = $this->_dms->getDB();
3262
3263        $cachehash = substr(md5($listadmin.$listowner.$listguest), 0, 3);
3264        if (!isset($this->_readAccessList[$cachehash])) {
3265            $this->_readAccessList[$cachehash] = array("groups" => array(), "users" => array());
3266            $userIDs = "";
3267            $groupIDs = "";
3268            $defAccess  = $this->getDefaultAccess();
3269
3270            /* Check if the default access is < read access or >= read access.
3271             * If default access is less than read access, then create a list
3272             * of users and groups with read access.
3273             * If default access is equal or greater then read access, then
3274             * create a list of users and groups without read access.
3275             */
3276            if ($defAccess<M_READ) {
3277                // Get the list of all users and groups that are listed in the ACL as
3278                // having read access to the document.
3279                $tmpList = $this->getAccessList(M_READ, O_GTEQ);
3280            }
3281            else {
3282                // Get the list of all users and groups that DO NOT have read access
3283                // to the document.
3284                $tmpList = $this->getAccessList(M_NONE, O_LTEQ);
3285            }
3286            /** @var SeedDMS_Core_GroupAccess $groupAccess */
3287            foreach ($tmpList["groups"] as $groupAccess) {
3288                $groupIDs .= (strlen($groupIDs)==0 ? "" : ", ") . $groupAccess->getGroupID();
3289            }
3290
3291            /** @var SeedDMS_Core_UserAccess $userAccess */
3292            foreach ($tmpList["users"] as $userAccess) {
3293                $user = $userAccess->getUser();
3294//                if (!$listadmin && $user->isAdmin()) continue;
3295//                if (!$listowner && $user->getID() == $this->_ownerID) continue;
3296//                if (!$listguest && $user->isGuest()) continue;
3297                $userIDs .= (strlen($userIDs)==0 ? "" : ", ") . $user->getID();
3298            }
3299
3300            // Construct a query against the users table to identify those users
3301            // that have read access on this document, either directly through an
3302            // ACL entry, by virtue of ownership or by having administrative rights
3303            // on the database.
3304            $queryStr="";
3305            /* If default access is less then read, $userIDs and $groupIDs contains
3306             * a list of user with read access
3307             */
3308            if ($defAccess < M_READ) {
3309                $queryStr = "SELECT `tblUsers`.* FROM `tblUsers` ".
3310                    "LEFT JOIN `tblGroupMembers` ON `tblGroupMembers`.`userID`=`tblUsers`.`id` ".
3311                    "LEFT JOIN `tblRoles` ON `tblRoles`.`id`=`tblUsers`.`role` ".
3312                    "WHERE 1=0".
3313                    ((strlen($groupIDs) > 0) ? " OR (`tblGroupMembers`.`groupID` IN (". $groupIDs ."))" : "").
3314                    ((strlen($userIDs) > 0) ?  " OR (`tblUsers`.`id` IN (". $userIDs ."))" : "").
3315                    " OR (`tblRoles`.`role` = ".SeedDMS_Core_Role::role_admin.")".
3316                    " OR (`tblUsers`.`id` = ". $this->_ownerID . ")".
3317                    " ORDER BY `login`";
3318            }
3319            /* If default access is equal or greater than M_READ, $userIDs and
3320             * $groupIDs contains a list of user without read access
3321             * The sql statement will exclude those users and groups but include
3322             * admins and the owner
3323             */
3324            else {
3325                $queryStr = "SELECT `tblUsers`.* FROM `tblUsers` ".
3326                    "LEFT JOIN `tblGroupMembers` ON `tblGroupMembers`.`userID`=`tblUsers`.`id` ".
3327                    "LEFT JOIN `tblRoles` ON `tblRoles`.`id`=`tblUsers`.`role` ".
3328                    "WHERE 1=1".
3329                    (strlen($groupIDs) == 0 ? "" : " AND (`tblGroupMembers`.`groupID` NOT IN (". $groupIDs .") OR `tblGroupMembers`.`groupID` IS NULL)").
3330                    (strlen($userIDs) == 0 ? "" : " AND (`tblUsers`.`id` NOT IN (". $userIDs ."))").
3331                    " OR `tblUsers`.`id` = ". $this->_ownerID . " OR `tblRoles`.`role` = ".SeedDMS_Core_Role::role_admin." ORDER BY `login` ";
3332            }
3333            $resArr = $db->getResultArray($queryStr);
3334            if (!is_bool($resArr)) {
3335                foreach ($resArr as $row) {
3336                    $user = $this->_dms->getUser($row['id']);
3337                    if (!$listadmin && $user->isAdmin()) continue;
3338                    if (!$listowner && $user->getID() == $this->_ownerID) continue;
3339                    if (!$listguest && $user->isGuest()) continue;
3340                    $this->_readAccessList[$cachehash]["users"][] = $user;
3341                }
3342            }
3343
3344            // Assemble the list of groups that have read access to the document.
3345            $queryStr="";
3346            if ($defAccess < M_READ) {
3347                if (strlen($groupIDs)>0) {
3348                    $queryStr = "SELECT `tblGroups`.* FROM `tblGroups` ".
3349                        "WHERE `tblGroups`.`id` IN (". $groupIDs .") ORDER BY `name`";
3350                }
3351            }
3352            else {
3353                if (strlen($groupIDs)>0) {
3354                    $queryStr = "SELECT `tblGroups`.* FROM `tblGroups` ".
3355                        "WHERE `tblGroups`.`id` NOT IN (". $groupIDs .") ORDER BY `name`";
3356                }
3357                else {
3358                    $queryStr = "SELECT `tblGroups`.* FROM `tblGroups` ORDER BY `name`";
3359                }
3360            }
3361            if (strlen($queryStr)>0) {
3362                $resArr = $db->getResultArray($queryStr);
3363                if (!is_bool($resArr)) {
3364                    foreach ($resArr as $row) {
3365                        $group = $this->_dms->getGroup($row["id"]);
3366                        $this->_readAccessList[$cachehash]["groups"][] = $group;
3367                    }
3368                }
3369            }
3370        }
3371        return $this->_readAccessList[$cachehash];
3372    } /* }}} */
3373
3374    /**
3375     * Get the internally used folderList which stores the ids of folders from
3376     * the root folder to the parent folder.
3377     *
3378     * @return string column separated list of folder ids
3379     */
3380    public function getFolderList() { /* {{{ */
3381        $db = $this->_dms->getDB();
3382
3383        $queryStr = "SELECT `folderList` FROM `tblDocuments` WHERE id = ".$this->_id;
3384        $resArr = $db->getResultArray($queryStr);
3385        if (is_bool($resArr) && !$resArr)
3386            return false;
3387
3388        return $resArr[0]['folderList'];
3389    } /* }}} */
3390
3391    /**
3392     * Checks the internal data of the document and repairs it.
3393     * Currently, this function only repairs an incorrect folderList
3394     *
3395     * @return boolean true on success, otherwise false
3396     */
3397    public function repair() { /* {{{ */
3398        $db = $this->_dms->getDB();
3399
3400        $curfolderlist = $this->getFolderList();
3401
3402        // calculate the folderList of the folder
3403        $parent = $this->getFolder();
3404        $pathPrefix="";
3405        $path = $parent->getPath();
3406        foreach ($path as $f) {
3407            $pathPrefix .= ":".$f->getID();
3408        }
3409        if (strlen($pathPrefix)>1) {
3410            $pathPrefix .= ":";
3411        }
3412        if($curfolderlist != $pathPrefix) {
3413            $queryStr = "UPDATE `tblDocuments` SET `folderList`='".$pathPrefix."' WHERE `id` = ". $this->_id;
3414            $res = $db->getResult($queryStr);
3415            if (!$res)
3416                return false;
3417        }
3418        return true;
3419    } /* }}} */
3420
3421    /**
3422     * Calculate the disk space including all versions of the document
3423     *
3424     * This is done by using the internal database field storing the
3425     * filesize of a document version.
3426     *
3427     * @return integer total disk space in Bytes
3428     */
3429    public function getUsedDiskSpace(): int { /* {{{ */
3430        $db = $this->_dms->getDB();
3431
3432        $queryStr = "SELECT SUM(`fileSize`) sum FROM `tblDocumentContent` WHERE `document` = " . $this->_id;
3433        $resArr = $db->getResultArray($queryStr);
3434        if (is_bool($resArr) && $resArr == false)
3435            return false;
3436
3437        return (int) $resArr[0]['sum'];
3438    } /* }}} */
3439
3440    /**
3441     * Returns a list of events happend during the life of the document
3442     *
3443     * This includes the creation of new versions, approval and reviews, etc.
3444     *
3445     * @return array list of events
3446     */
3447    public function getTimeline() { /* {{{ */
3448        $db = $this->_dms->getDB();
3449
3450        $timeline = array();
3451
3452        $lc=$this->getLatestContent();
3453        $queryStr = "SELECT `revisiondate`, `version` FROM `tblDocumentContent` WHERE `document` = " . $this->_id . " AND `version` = " . $lc->getVersion();
3454        $resArr = $db->getResultArray($queryStr);
3455        if (is_bool($resArr) && $resArr == false)
3456            return false;
3457
3458        foreach ($resArr as $row) {
3459            if($row['revisiondate'] && substr($row['revisiondate'], 0, 4) != '0000')
3460                $timeline[] = array('date'=>substr($row['revisiondate'], 0, 10)." 00:00:00", 'allday'=>true, 'msg'=>'Scheduled revision of version '.$row['version'], 'type'=>'scheduled_revision', 'version'=>$row['version'], 'document'=>$this, 'params'=>array($row['version']));
3461        }
3462
3463        $queryStr = "SELECT * FROM `tblDocumentFiles` WHERE `document` = " . $this->_id;
3464        $resArr = $db->getResultArray($queryStr);
3465        if (is_bool($resArr) && $resArr == false)
3466            return false;
3467
3468        foreach ($resArr as $row) {
3469            $date = date('Y-m-d H:i:s', (int) $row['date']);
3470            $timeline[] = array('date'=>$date, 'msg'=>'Added attachment "'.$row['name'].'"', 'document'=>$this, 'type'=>'add_file', 'fileid'=>$row['id']);
3471        }
3472
3473        $queryStr=
3474            "SELECT `tblDocumentStatus`.*, `tblDocumentStatusLog`.`statusLogID`,`tblDocumentStatusLog`.`status`, ".
3475            "`tblDocumentStatusLog`.`comment`, `tblDocumentStatusLog`.`date`, ".
3476            "`tblDocumentStatusLog`.`userID` ".
3477            "FROM `tblDocumentStatus` ".
3478            "LEFT JOIN `tblDocumentStatusLog` USING (`statusID`) ".
3479            "WHERE `tblDocumentStatus`.`documentID` = '". $this->_id ."' ".
3480            "ORDER BY `tblDocumentStatusLog`.`statusLogID` DESC";
3481        $resArr = $db->getResultArray($queryStr);
3482        if (is_bool($resArr) && !$resArr)
3483            return false;
3484
3485        /* The above query will also contain entries where a document status exists
3486         * but no status log entry. Those records will have no date and must be
3487         * skipped.
3488         */
3489        foreach ($resArr as $row) {
3490            if($row['date']) {
3491                $date = $row['date'];
3492                $timeline[] = array('date'=>$date, 'msg'=>'Version '.$row['version'].': Status change to '.$row['status'], 'type'=>'status_change', 'version'=>$row['version'], 'document'=>$this, 'status'=>$row['status'], 'statusid'=>$row['statusID'], 'statuslogid'=>$row['statusLogID']);
3493            }
3494        }
3495        return $timeline;
3496    } /* }}} */
3497
3498    /**
3499     * Transfers the document to a new user
3500     * 
3501     * This method not just sets a new owner of the document but also
3502     * transfers the document links, attachments and locks to the new user.
3503     *
3504     * @return boolean true if successful, otherwise false
3505     */
3506    public function transferToUser($newuser) { /* {{{ */
3507        $db = $this->_dms->getDB();
3508
3509        if($newuser->getId() == $this->_ownerID)
3510            return true;
3511
3512        $db->startTransaction();
3513        $queryStr = "UPDATE `tblDocuments` SET `owner` = ".$newuser->getId()." WHERE `id` = " . $this->_id;
3514        if (!$db->getResult($queryStr)) {
3515            $db->rollbackTransaction();
3516            return false;
3517        }
3518
3519        $queryStr = "UPDATE `tblDocumentLocks` SET `userID` = ".$newuser->getId()." WHERE `document` = " . $this->_id . " AND `userID` = ".$this->_ownerID;
3520        if (!$db->getResult($queryStr)) {
3521            $db->rollbackTransaction();
3522            return false;
3523        }
3524
3525        $queryStr = "UPDATE `tblDocumentLinks` SET `userID` = ".$newuser->getId()." WHERE `document` = " . $this->_id . " AND `userID` = ".$this->_ownerID;
3526        if (!$db->getResult($queryStr)) {
3527            $db->rollbackTransaction();
3528            return false;
3529        }
3530
3531        $queryStr = "UPDATE `tblDocumentFiles` SET `userID` = ".$newuser->getId()." WHERE `document` = " . $this->_id . " AND `userID` = ".$this->_ownerID;
3532        if (!$db->getResult($queryStr)) {
3533            $db->rollbackTransaction();
3534            return false;
3535        }
3536
3537        $this->_ownerID = $newuser->getID();
3538        $this->_owner = $newuser;
3539
3540        $db->commitTransaction();
3541        return true;
3542    } /* }}} */
3543
3544} /* }}} */
3545
3546
3547/**
3548 * Class to represent content of a document
3549 *
3550 * Each document has content attached to it, often called a 'version' of the
3551 * document. The document content represents a file on the disk with some
3552 * meta data stored in the database. A document content has a version number
3553 * which is incremented with each replacement of the old content. Old versions
3554 * are kept unless they are explicitly deleted by
3555 * {@see SeedDMS_Core_Document::removeContent()}.
3556 *
3557 * @category   DMS
3558 * @package    SeedDMS_Core
3559 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
3560 *             Uwe Steinmann <uwe@steinmann.cx>
3561 * @copyright  Copyright (C) 2002-2005 Markus Westphal,
3562 *             2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli,
3563 *             2010-2024 Uwe Steinmann
3564 * @version    Release: @package_version@
3565 */
3566class SeedDMS_Core_DocumentContent extends SeedDMS_Core_Object { /* {{{ */
3567    /**
3568     * @var object document
3569     */
3570    protected $_document;
3571
3572    /**
3573     * @var integer version
3574     */
3575    protected $_version;
3576
3577    /**
3578     * @var string comment
3579     */
3580    protected $_comment;
3581
3582    /**
3583     * @var string date
3584     */
3585    protected $_date;
3586
3587    /**
3588     * @var integer $_userID
3589     */
3590    protected $_userID;
3591
3592    /**
3593     * @var object $_user
3594     */
3595    protected $_user;
3596
3597    /**
3598     * @var string dir on disk (deprecated)
3599     */
3600    protected $_dir;
3601
3602    /**
3603     * @var string original file name
3604     */
3605    protected $_orgFileName;
3606
3607    /**
3608     * @var string file type (actually the extension without the leading dot)
3609     */
3610    protected $_fileType;
3611
3612    /**
3613     * @var string mime type
3614     */
3615    protected $_mimeType;
3616
3617    /**
3618     * @var string checksum of content
3619     */
3620    protected $_checksum;
3621
3622    /**
3623     * @var int size of content file
3624     */
3625    protected $_fileSize;
3626
3627    /**
3628     * @var object workflow
3629     */
3630    protected $_workflow;
3631
3632    /**
3633     * @var object workflow state
3634     */
3635    protected $_workflowState;
3636
3637    /**
3638     * @var int $_status state
3639     */
3640    protected $_status;
3641
3642    /**
3643     * @var int $_reviewStatus state
3644     */
3645    protected $_reviewStatus;
3646
3647    /**
3648     * @var int $_approvalStatus state
3649     */
3650    protected $_approvalStatus;
3651
3652    /**
3653     * @var int $_receiptStatus state
3654     */
3655    protected $_receiptStatus;
3656
3657    /**
3658     * @var int $_revisionStatus state
3659     */
3660    protected $_revisionStatus;
3661
3662    /**
3663     * @var string date of revision
3664     */
3665    protected $_revisionDate;
3666
3667    /**
3668     * @var object dms
3669     */
3670    public $_dms;
3671
3672    /**
3673     * Recalculate the status of a document
3674     *
3675     * The methods checks the review and approval status and sets the
3676     * status of the document accordingly.
3677     *
3678     * If status is S_RELEASED and the version has a workflow, then set
3679     * the status to S_IN_WORKFLOW
3680     * If status is S_RELEASED and there are reviewers => set status S_DRAFT_REV
3681     * If status is S_RELEASED or S_DRAFT_REV and there are approvers => set
3682     * status S_DRAFT_APP
3683     * If status is draft and there are no approver and no reviewers => set
3684     * status to S_RELEASED
3685     * The status of a document with the current status S_OBSOLETE, S_REJECTED,
3686     * S_NEEDS_CORRECTION or S_EXPIRED will not be changed unless the parameter
3687     * $ignorecurrentstatus is set to true.
3688     *
3689     * This method may not be called after a negative approval or review to
3690     * recalculated the status, because
3691     * it doesn't take a defeating approval or review into account. This method
3692     * does not set the status to S_REJECTED! It will
3693     * just check for a pending workflow, approval or review and set the status
3694     * accordingly, e.g. after the list of reviewers or appovers has been
3695     * modified. If there is no pending workflow, approval or review the
3696     * status will be set to S_RELEASED.
3697     *
3698     * This method will call {@see SeedDMS_Core_DocumentContent::setStatus()}
3699     * which checks if the status has actually changed. This is, why this
3700     * function can be called at any time without harm to the status log.
3701     * The $initialstatus can be set, to define the status set when no other
3702     * status is set. This happens if the document has no
3703     *
3704     * @param boolean $ignorecurrentstatus ignore the current status and
3705     *        recalculate a new status in any case
3706     * @param object $user the user initiating this method
3707     * @param string $msg message stored in status log when status is set
3708     * @param integer $initialstatus status to be set if no other status is set
3709     */
3710    function verifyStatus($ignorecurrentstatus=false, $user=null, $msg='', $initialstatus=S_RELEASED) { /* {{{ */
3711
3712        unset($this->_status);
3713        $st=$this->getStatus();
3714
3715        /* Documents already obsoleted, rejected or expired will not change
3716         * its status anymore, unless explicitly requested. Be aware, that
3717         * this method has an unsufficient check for negative reviews and
3718         * approvals. A document in status S_REJECTED may become S_RELEASED
3719         * if there is at least one positive review or approval.
3720         */
3721        if (!$ignorecurrentstatus && ($st["status"]==S_OBSOLETE || $st["status"]==S_REJECTED || $st["status"]==S_EXPIRED || $st["status"]==S_NEEDS_CORRECTION)) return $st['status'];
3722
3723        $this->_workflow = null; // force to be reloaded from DB
3724        $hasworkflow = $this->getWorkflow() ? true : false;
3725
3726        /* $pendingReview will be set when there are still open reviews */
3727        $pendingReview=false;
3728        /* $hasReview will be set if there is at least one positiv review */
3729        $hasReview=false;
3730        unset($this->_reviewStatus);  // force to be reloaded from DB
3731        $reviewStatus=$this->getReviewStatus();
3732        if (is_array($reviewStatus) && count($reviewStatus)>0) {
3733            foreach ($reviewStatus as $r){
3734                if ($r["status"]==0){
3735                    $pendingReview=true;
3736                    break;
3737                } elseif($r["status"]==1){
3738                    $hasReview=true;
3739                }
3740            }
3741        }
3742
3743        /* $pendingApproval will be set when there are still open approvals */
3744        $pendingApproval=false;
3745        /* $hasApproval will be set if there is at least one positiv review */
3746        $hasApproval=false;
3747        unset($this->_approvalStatus);  // force to be reloaded from DB
3748        $approvalStatus=$this->getApprovalStatus();
3749        if (is_array($approvalStatus) && count($approvalStatus)>0) {
3750            foreach ($approvalStatus as $a){
3751                if ($a["status"]==0){
3752                    $pendingApproval=true;
3753                    break;
3754                } elseif($a["status"]==1){
3755                    $hasApproval=true;
3756                }
3757            }
3758        }
3759        $pendingRevision=false;
3760        $hasRevision=false;
3761        $needsCorrection=false;
3762        unset($this->_revisionStatus);  // force to be reloaded from DB
3763        $revsisionStatus=$this->getRevisionStatus();
3764        if (is_array($revsisionStatus) && count($revsisionStatus)>0) {
3765            foreach ($revsisionStatus as $a){
3766                if ($a["status"]==0){
3767                    $pendingRevision=true;
3768                    break;
3769                } elseif($a["status"]==1){
3770                    $hasRevision=true;
3771                } elseif($a["status"]==-1){
3772                    $needsCorrection=true;
3773                }
3774            }
3775        }
3776
3777        $ret = false;
3778        /* First check for a running workflow or open reviews, approvals, revisions. */
3779        if ($hasworkflow) { $newstatus = S_IN_WORKFLOW; $ret = $this->setStatus(S_IN_WORKFLOW,$msg,$user); }
3780        elseif ($pendingReview) { $newstatus = S_DRAFT_REV; $ret = $this->setStatus(S_DRAFT_REV,$msg,$user); }
3781        elseif ($pendingApproval) { $newstatus = S_DRAFT_APP; $ret = $this->setStatus(S_DRAFT_APP,$msg,$user); }
3782        elseif ($pendingRevision) { $newstatus = S_IN_REVISION; $ret = $this->setStatus(S_IN_REVISION,$msg,$user); }
3783        /* This point will only be reached if there is no pending workflow, review,
3784         * approval or revision but the current status is one of S_DRAFT_REV,
3785         * S_DRAFT_APP or S_IN_REVISION. This can happen if formely set reviewers,
3786         * approvers, revisors are completly removed. In case of S_DRAFT_REV and
3787         * S_DRAFT_APP the document will go back into its initial status. If a
3788         * positive review or approval was found the document will be released.
3789         * Be aware that negative reviews or approvals are not taken into account,
3790         * because in that case the document must have been rejected before calling
3791         * this function. FIXME: this is a problem if the parameter $ignorecurrentstatus
3792         * was set, because an already rejected document may be released with just
3793         * one positive review or approval disregarding any negative reviews or
3794         * approvals.
3795         * A document in status S_IN_REVISION will be treated differently.
3796         * It takes negative revisions into account!
3797         *
3798         * A document in status S_DRAFT will never go into S_RELEASED and document
3799         * already released will never go back at this point into the given
3800         * initial status, which can only by S_DRAFT or S_RELEASED
3801         */
3802        elseif ($st["status"]!=S_DRAFT && $st["status"]!=S_RELEASED ) {
3803            if($st["status"]==S_DRAFT_REV || $st["status"]==S_DRAFT_APP) {
3804                if($hasReview || $hasApproval) { $newstatus = S_RELEASED; $ret = $this->setStatus(S_RELEASED,$msg,$user); }
3805                else { $newstatus = $initialstatus; $ret = $this->setStatus($initialstatus,$msg,$user); }
3806            } elseif($st["status"]==S_IN_REVISION) {
3807                if($needsCorrection) { $newstatus = S_NEEDS_CORRECTION; $ret = $this->setStatus(S_NEEDS_CORRECTION,$msg,$user); }
3808                else {
3809                    $newstatus = S_RELEASED;
3810                    $ret = $this->finishRevision($user, S_RELEASED, 'Finished revision workflow', $msg);
3811                }
3812            } elseif($st["status"]==S_EXPIRED) {
3813                $newstatus = S_RELEASED; $ret = $this->setStatus(S_RELEASED,$msg,$user);
3814            } elseif($st["status"]==S_IN_WORKFLOW) {
3815                $newstatus = $initialstatus; $ret = $this->setStatus($initialstatus,$msg,$user);
3816            }
3817        }
3818
3819        return $ret ? $newstatus : $ret;
3820    } /* }}} */
3821
3822    public function __construct($id, $document, $version, $comment, $date, $userID, $dir, $orgFileName, $fileType, $mimeType, $fileSize=0, $checksum='', $revisionDate=null) { /* {{{ */
3823        parent::__construct($id);
3824        $this->_document = $document;
3825        $this->_version = (int) $version;
3826        $this->_comment = $comment;
3827        $this->_date = (int) $date;
3828        $this->_userID = (int) $userID;
3829        $this->_user = null;
3830        $this->_dir = $dir;
3831        $this->_orgFileName = $orgFileName;
3832        $this->_fileType = $fileType;
3833        $this->_mimeType = $mimeType;
3834        $this->_dms = $document->getDMS();
3835        if(!$fileSize) {
3836            $this->_fileSize = SeedDMS_Core_File::fileSize($this->_dms->contentDir . $this->getPath());
3837        } else {
3838            $this->_fileSize = (int) $fileSize;
3839        }
3840        $this->_checksum = $checksum;
3841        $this->_workflow = null;
3842        $this->_workflowState = null;
3843        $this->_revisionDate = $revisionDate;
3844    } /* }}} */
3845
3846    /**
3847     * Return an document content by its id
3848     *
3849     * @param integer $id id of document
3850     * @param SeedDMS_Core_DMS $dms
3851     * @return bool|SeedDMS_Core_DocumentContent instance of SeedDMS_Core_DocumentContent
3852     * if document content exists, null if document does not exist, false in case of error
3853     */
3854    public static function getInstance($id, $dms) { /* {{{ */
3855        $db = $dms->getDB();
3856
3857        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `id` = " . (int) $id;
3858        $resArr = $db->getResultArray($queryStr);
3859        if (is_bool($resArr) && $resArr == false)
3860            return false;
3861        if (count($resArr) != 1)
3862            return null;
3863        $row = $resArr[0];
3864
3865        $classname = $dms->getClassname('documentcontent');
3866        $user = $dms->getLoggedInUser();
3867        $document = $dms->getDocument($row['document']);
3868        $document->setDMS($dms);
3869        /** @var SeedDMS_Core_DocumentContent $documentcontent */
3870        $content = new $classname($row["id"], $document, $row["version"], $row["comment"], $row["date"], $row["createdBy"], $row["dir"], $row["orgFileName"], $row["fileType"], $row["mimeType"], $row['fileSize'], $row['checksum'], $row['revisiondate']);
3871        if($user) {
3872            if($content->getAccessMode($user) >= M_READ)
3873                return $content;
3874        } else {
3875            return $content;
3876        }
3877        return null;
3878    } /* }}} */
3879
3880    /**
3881     * Check if this object is of type 'documentcontent'.
3882     *
3883     * @param string $type type of object
3884     */
3885    public function isType($type) { /* {{{ */
3886        return $type == 'documentcontent';
3887    } /* }}} */
3888
3889    public function getVersion() { return $this->_version; }
3890    public function getComment() { return $this->_comment; }
3891    public function getDate() { return $this->_date; }
3892    public function getOriginalFileName() { return $this->_orgFileName; }
3893    public function getFileType() { return $this->_fileType; }
3894    public function getFileName(){ return $this->_version . $this->_fileType; }
3895    /**
3896     * getDir and the corresponding database table field are deprecated
3897     */
3898    private function __getDir() { return $this->_dir; }
3899    public function getMimeType() { return $this->_mimeType; }
3900    public function getRevisionDate() { return $this->_revisionDate; }
3901    public function getDocument() { return $this->_document; }
3902
3903    public function getUser() { /* {{{ */
3904        if (!isset($this->_user))
3905            $this->_user = $this->_document->getDMS()->getUser($this->_userID);
3906        return $this->_user;
3907    } /* }}} */
3908
3909    /**
3910     * Return path of file on disk relative to the content directory
3911     *
3912     * Since version 5.1.13 a single '.' in the fileType will be skipped.
3913     * On Windows a file named 'name.' will be saved as 'name' but the fileType
3914     * will contain the a single '.'.
3915     *
3916     * @return string path of file on disc
3917     */
3918    public function getPath() { return $this->_document->getDir() . $this->_version . $this->_fileType; }
3919
3920    function setRevisionDate($date = false) { /* {{{ */
3921        $db = $this->_document->getDMS()->getDB();
3922
3923        if(!$date)
3924            $queryStr = "UPDATE `tblDocumentContent` SET `revisiondate` = null WHERE `document` = " . $this->_document->getID() .    " AND `version` = " . $this->_version;
3925        elseif($date == 'now')
3926            $queryStr = "UPDATE `tblDocumentContent` SET `revisiondate` = ".$db->getCurrentDatetime()." WHERE `document` = " . $this->_document->getID() .    " AND `version` = " . $this->_version;
3927        else
3928            $queryStr = "UPDATE `tblDocumentContent` SET `revisiondate` = ".$db->qstr($date)." WHERE `document` = " . $this->_document->getID() .    " AND `version` = " . $this->_version;
3929        if (!$db->getResult($queryStr))
3930            return false;
3931
3932        $this->_revisionDate = $date;
3933
3934        return true;
3935    } /* }}} */
3936
3937    /**
3938     * Set upload date of document content
3939     *
3940     * @param string $date date must be a timestamp or in the format 'Y-m-d H:i:s'
3941     *
3942     * @return boolean true on success, otherwise false
3943     */
3944    public function setDate($date = false) { /* {{{ */
3945        $db = $this->_document->getDMS()->getDB();
3946
3947        if(!$date)
3948            $date = time();
3949        else {
3950            if(is_string($date) && SeedDMS_Core_DMS::checkDate($date, 'Y-m-d H:i:s')) {
3951                $date = strtotime($date);
3952            } elseif(is_numeric($date))
3953                $date = (int) $date;
3954            else
3955                return false;
3956        }
3957
3958        $queryStr = "UPDATE `tblDocumentContent` SET `date` = ". $date." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
3959        if (!$db->getResult($queryStr))
3960            return false;
3961
3962        $this->_date = $date;
3963
3964        return true;
3965    } /* }}} */
3966
3967    public function getFileSize() { /* {{{ */
3968        return $this->_fileSize;
3969    } /* }}} */
3970
3971    /**
3972     * Set file size by reading the file
3973     */
3974    public function setFileSize() { /* {{{ */
3975        $filesize = SeedDMS_Core_File::fileSize($this->_dms->contentDir . $this->_document->getDir() . $this->getFileName());
3976        if($filesize === false)
3977            return false;
3978
3979        $db = $this->_document->getDMS()->getDB();
3980        $queryStr = "UPDATE `tblDocumentContent` SET `fileSize` = ".$filesize." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
3981        if (!$db->getResult($queryStr))
3982            return false;
3983        $this->_fileSize = $filesize;
3984
3985        return true;
3986    } /* }}} */
3987
3988    public function getChecksum() { /* {{{ */
3989        return $this->_checksum;
3990    } /* }}} */
3991
3992    /**
3993     * Set checksum by reading the file
3994     */
3995    public function setChecksum() { /* {{{ */
3996        $checksum = SeedDMS_Core_File::checksum($this->_dms->contentDir . $this->_document->getDir() . $this->getFileName());
3997        if($checksum === false)
3998            return false;
3999
4000        $db = $this->_document->getDMS()->getDB();
4001        $queryStr = "UPDATE `tblDocumentContent` SET `checksum` = ".$db->qstr($checksum)." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
4002        if (!$db->getResult($queryStr))
4003            return false;
4004        $this->_checksum = $checksum;
4005
4006        return true;
4007    } /* }}} */
4008
4009    /**
4010     * Set file type by evaluating the mime type
4011     */
4012    public function setFileType() { /* {{{ */
4013        $mimetype = $this->getMimeType();
4014
4015        $expect = SeedDMS_Core_File::fileExtension($mimetype);
4016        if($expect && '.'.$expect != $this->_fileType) {
4017            $db = $this->_document->getDMS()->getDB();
4018            $db->startTransaction();
4019            $queryStr = "UPDATE `tblDocumentContent` SET `fileType`='.".$expect."' WHERE `id` =   ". $this->_id;
4020            $res = $db->getResult($queryStr);
4021            if ($res) {
4022                if(!SeedDMS_Core_File::renameFile($this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType, $this->_dms->contentDir.$this->_document->getDir() . $this->_version . '.' . $expect)) {
4023                    $db->rollbackTransaction();
4024                } else {
4025                    $this->_fileType = '.'.$expect;
4026                    $db->commitTransaction();
4027                    return true;
4028                }
4029            } else {
4030                $db->rollbackTransaction();
4031            }
4032        }
4033
4034        return false;
4035    } /* }}} */
4036
4037    public function setMimeType($newMimetype) { /* {{{ */
4038        $db = $this->_document->getDMS()->getDB();
4039
4040        if(!$newMimetype)
4041            return false;
4042
4043        $newMimetype = trim($newMimetype);
4044
4045        if(!$newMimetype)
4046            return false;
4047
4048        $queryStr = "UPDATE `tblDocumentContent` SET `mimeType` = ".$db->qstr($newMimetype)." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
4049        if (!$db->getResult($queryStr))
4050            return false;
4051
4052        $this->_mimeType = $newMimetype;
4053
4054        return true;
4055    } /* }}} */
4056
4057    public function setComment($newComment) { /* {{{ */
4058        $db = $this->_document->getDMS()->getDB();
4059
4060        /* Check if 'onPreSetVersionComment' callback is set */
4061        if(isset($this->_dms->callbacks['onPreSetVersionComment'])) {
4062            foreach($this->_dms->callbacks['onPreSetVersionComment'] as $callback) {
4063                $ret = call_user_func($callback[0], $callback[1], $this, $newComment);
4064                if(is_bool($ret))
4065                    return $ret;
4066            }
4067        }
4068
4069        $queryStr = "UPDATE `tblDocumentContent` SET `comment` = ".$db->qstr($newComment)." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
4070        if (!$db->getResult($queryStr))
4071            return false;
4072
4073        $this->_comment = $newComment;
4074
4075        /* Check if 'onPostSetVersionComment' callback is set */
4076        if(isset($this->_dms->callbacks['onPostSetVersionComment'])) {
4077            foreach($this->_dms->callbacks['onPostSetVersionComment'] as $callback) {
4078                $ret = call_user_func($callback[0], $callback[1], $this, $oldComment);
4079                if(is_bool($ret))
4080                    return $ret;
4081            }
4082        }
4083
4084        return true;
4085    } /* }}} */
4086
4087    /**
4088     * Get the latest status of the content
4089     *
4090     * The status of the content reflects its current review, approval or workflow
4091     * state. A status can be a negative or positive number or 0. A negative
4092     * numbers indicate a missing approval, review or an obsolete content.
4093     * Positive numbers indicate some kind of approval or workflow being
4094     * active, but not necessarily a release.
4095     * S_DRAFT_REV, 0
4096     * S_DRAFT_APP, 1
4097     * S_RELEASED, 2
4098     * S_IN_WORKFLOW, 3
4099     * S_IN_REVISION, 4
4100     * S_REJECTED, -1
4101     * S_OBSOLETE, -2
4102     * S_EXPIRED, -3
4103     * When a content is inserted and does not need approval nor review,
4104     * then its status is set to S_RELEASED immediately. Any change of
4105     * the status is monitored in the table tblDocumentStatusLog. This
4106     * function will always return the latest entry for the content.
4107     *
4108     * @return array latest record from tblDocumentStatusLog
4109     */
4110    public function getStatus($limit=1) { /* {{{ */
4111        $db = $this->_document->getDMS()->getDB();
4112
4113        if (!is_numeric($limit)) return false;
4114
4115        // Retrieve the current overall status of the content represented by
4116        // this object.
4117        if (!isset($this->_status)) {
4118            $queryStr=
4119                "SELECT `tblDocumentStatus`.*, `tblDocumentStatusLog`.`status`, ".
4120                "`tblDocumentStatusLog`.`comment`, `tblDocumentStatusLog`.`date`, ".
4121                "`tblDocumentStatusLog`.`userID` ".
4122                "FROM `tblDocumentStatus` ".
4123                "LEFT JOIN `tblDocumentStatusLog` USING (`statusID`) ".
4124                "WHERE `tblDocumentStatus`.`documentID` = '". $this->_document->getID() ."' ".
4125                "AND `tblDocumentStatus`.`version` = '". $this->_version ."' ".
4126                "ORDER BY `tblDocumentStatusLog`.`statusLogID` DESC LIMIT ".(int) $limit;
4127
4128            $res = $db->getResultArray($queryStr);
4129            if (is_bool($res) && !$res)
4130                return false;
4131            if (count($res)!=1)
4132                return false;
4133            $this->_status = $res[0];
4134        }
4135        return $this->_status;
4136    } /* }}} */
4137
4138    /**
4139     * Get current and former states of the document content
4140     *
4141     * @param integer $limit if not set all log entries will be returned
4142     * @return array list of status changes
4143     */
4144    public function getStatusLog($limit=0) { /* {{{ */
4145        $db = $this->_document->getDMS()->getDB();
4146
4147        if (!is_numeric($limit)) return false;
4148
4149        $queryStr=
4150            "SELECT `tblDocumentStatus`.*, `tblDocumentStatusLog`.`status`, ".
4151            "`tblDocumentStatusLog`.`comment`, `tblDocumentStatusLog`.`date`, ".
4152            "`tblDocumentStatusLog`.`userID` ".
4153            "FROM `tblDocumentStatus` ".
4154            "LEFT JOIN `tblDocumentStatusLog` USING (`statusID`) ".
4155            "WHERE `tblDocumentStatus`.`documentID` = '". $this->_document->getID() ."' ".
4156            "AND `tblDocumentStatus`.`version` = '". $this->_version ."' ".
4157            "ORDER BY `tblDocumentStatusLog`.`statusLogID` DESC ";
4158        if($limit)
4159            $queryStr .= "LIMIT ".(int) $limit;
4160
4161        $res = $db->getResultArray($queryStr);
4162        if (is_bool($res) && !$res)
4163            return false;
4164
4165        return $res;
4166    } /* }}} */
4167
4168    /**
4169     * Set the status of the content
4170     *
4171     * Setting the status means to add another entry into the table
4172     * tblDocumentStatusLog. The method returns also false if the status
4173     * is already set on the value passed to the method.
4174     *
4175     * @param integer $status     new status of content
4176     * @param string  $comment    comment for this status change
4177     * @param object  $updateUser user initiating the status change
4178     * @param string  $date       date in the format 'Y-m-d H:i:s'
4179     *
4180     * @return boolean true on success, otherwise false
4181     */
4182    public function setStatus(int $status, string $comment, $updateUser, $date='') { /* {{{ */
4183        $db = $this->_document->getDMS()->getDB();
4184
4185        if (!is_numeric($status)) return false;
4186
4187        /* return an error if $updateuser is not set */
4188        if(!$updateUser || !$updateUser->isType('user'))
4189            return false;
4190
4191        // If the supplied value lies outside of the accepted range, return an
4192        // error.
4193        if ($status < S_LOWEST_STATUS || $status > S_HIGHEST_STATUS) {
4194            return false;
4195        }
4196
4197        // Retrieve the current overall status of the content represented by
4198        // this object, if it hasn't been done already.
4199        if (!isset($this->_status)) {
4200            $this->getStatus();
4201        }
4202        if ($this->_status["status"]==$status) {
4203            return true;
4204        }
4205        if($date) {
4206            if(!SeedDMS_Core_DMS::checkDate($date, 'Y-m-d H:i:s'))
4207                return false;
4208            $ddate = $db->qstr($date);
4209        } else
4210            $ddate = $db->getCurrentDatetime();
4211        $db->startTransaction();
4212        $queryStr = "INSERT INTO `tblDocumentStatusLog` (`statusID`, `status`, `comment`, `date`, `userID`) ".
4213            "VALUES ('". $this->_status["statusID"] ."', '". (int) $status ."', ".$db->qstr($comment).", ".$ddate.", '". $updateUser->getID() ."')";
4214        $res = $db->getResult($queryStr);
4215        if (is_bool($res) && !$res) {
4216            $db->rollbackTransaction();
4217            return false;
4218        }
4219
4220        /* Check if 'onSetStatus' callback is set */
4221        if(isset($this->_dms->callbacks['onSetStatus'])) {
4222            foreach($this->_dms->callbacks['onSetStatus'] as $callback) {
4223                $ret = call_user_func($callback[0], $callback[1], $this, $updateUser, $this->_status["status"], $status);
4224                if(is_bool($ret)) {
4225                    unset($this->_status);
4226                    if($ret)
4227                        $db->commitTransaction();
4228                    else
4229                        $db->rollbackTransaction();
4230                    return $ret;
4231                }
4232            }
4233        }
4234
4235        $db->commitTransaction();
4236        unset($this->_status);
4237        return true;
4238    } /* }}} */
4239
4240    /**
4241     * Rewrites the complete status log
4242     *
4243     * Attention: this function is highly dangerous.
4244     * It removes an existing status log and rewrites it.
4245     * This method was added for importing an xml dump.
4246     *
4247     * @param array $statuslog new status log with the newest log entry first.
4248     * @return boolean true on success, otherwise false
4249     */
4250    public function rewriteStatusLog($statuslog) { /* {{{ */
4251        $db = $this->_document->getDMS()->getDB();
4252
4253        $queryStr= "SELECT `tblDocumentStatus`.* FROM `tblDocumentStatus` WHERE `tblDocumentStatus`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentStatus`.`version` = '". $this->_version ."' ";
4254        $res = $db->getResultArray($queryStr);
4255        if (is_bool($res) && !$res)
4256            return false;
4257
4258        $statusID = $res[0]['statusID'];
4259
4260        $db->startTransaction();
4261
4262        /* First, remove the old entries */
4263        $queryStr = "DELETE FROM `tblDocumentStatusLog` WHERE `statusID`=".$statusID;
4264        if (!$db->getResult($queryStr)) {
4265            $db->rollbackTransaction();
4266            return false;
4267        }
4268
4269        /* Second, insert the new entries */
4270        $statuslog = array_reverse($statuslog);
4271        foreach($statuslog as $log) {
4272            if(!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
4273                $db->rollbackTransaction();
4274                return false;
4275            }
4276            $queryStr = "INSERT INTO `tblDocumentStatusLog` (`statusID`, `status`, `comment`, `date`, `userID`) ".
4277                "VALUES ('".$statusID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".$log['user']->getID().")";
4278            if (!$db->getResult($queryStr)) {
4279                $db->rollbackTransaction();
4280                return false;
4281            }
4282        }
4283
4284        $db->commitTransaction();
4285        return true;
4286    } /* }}} */
4287
4288
4289    /**
4290     * Returns the access mode similar to a document
4291     *
4292     * There is no real access mode for document content, so this is more
4293     * like a virtual access mode, derived from the status of the document
4294     * content. The function checks if {@see SeedDMS_Core_DMS::noReadForStatus}
4295     * contains the status of the version and returns M_NONE if it exists and
4296     * the user is not involved in a workflow or review/approval/revision.
4297     * This method is called by all functions that returns the content e.g.
4298     * {@see SeedDMS_Core_Document::getLatestContent()}
4299     * It is also used by {@see SeedDMS_Core_Document::getAccessMode()} to
4300     * prevent access on the whole document if there is no accessible version.
4301     *
4302     * FIXME: This method only works propperly if $u is the currently logged in
4303     * user, because noReadForStatus will be set for this user.
4304     * FIXED: instead of using $dms->noReadForStatus it is take from the user's role
4305     *
4306     * @param object $u user
4307     * @return integer either M_NONE or M_READ
4308     */
4309    public function getAccessMode($u) { /* {{{ */
4310        $dms = $this->_document->getDMS();
4311
4312        /* Check if 'onCheckAccessDocumentContent' callback is set */
4313        if(isset($this->_dms->callbacks['onCheckAccessDocumentContent'])) {
4314            foreach($this->_dms->callbacks['onCheckAccessDocumentContent'] as $callback) {
4315                if(($ret = call_user_func($callback[0], $callback[1], $this, $u)) > 0) {
4316                    return $ret;
4317                }
4318            }
4319        }
4320
4321//        return M_READ;
4322
4323        if(!$u)
4324            return M_NONE;
4325
4326        /* If read access isn't further restricted by status, than grant read access */
4327        /* Old code
4328        if(!$dms->noReadForStatus)
4329            return M_READ;
4330        $noReadForStatus = $dms->noReadForStatus;
4331        */
4332        $noReadForStatus = $u->getRole()->getNoAccess();
4333        if(!$noReadForStatus)
4334            return M_READ;
4335
4336        /* If the current status is not in list of status without read access, then grant read access */
4337        if(!in_array($this->getStatus()['status'], $noReadForStatus))
4338            return M_READ;
4339
4340        /* Administrators have unrestricted access */
4341        if ($u->isAdmin()) return M_READ;
4342
4343        /* The owner of the document has unrestricted access */
4344        $owner = $this->_document->getOwner();
4345        if ($u->getID() == $owner->getID()) return M_READ;
4346
4347        /* Read/Write access on the document will also grant access on the version */
4348        if($this->_document->getAccessMode($u) >= M_READWRITE) return M_READ;
4349
4350        /* At this point the current status is in the list of status without read access.
4351         * The only way to still gain read access is, if the user is involved in the
4352         * process, e.g. is a reviewer, approver or an active person in the workflow.
4353         */
4354        $s = $this->getStatus();
4355        switch($s['status']) {
4356        case S_DRAFT_REV:
4357            $status = $this->getReviewStatus();
4358            foreach ($status as $r) {
4359                if($r['status'] != -2) // Check if reviewer was removed
4360                    switch ($r["type"]) {
4361                    case 0: // Reviewer is an individual.
4362                        if($u->getId() == $r["required"])
4363                            return M_READ;
4364                        break;
4365                    case 1: // Reviewer is a group.
4366                        $required = $dms->getGroup($r["required"]);
4367                        if (is_object($required) && $required->isMember($u))
4368                            return M_READ;
4369                        break;
4370                    }
4371            }
4372            break;
4373        case S_DRAFT_APP:
4374            $status = $this->getApprovalStatus();
4375            foreach ($status as $r) {
4376                if($r['status'] != -2) // Check if approver was removed
4377                    switch ($r["type"]) {
4378                    case 0: // Reviewer is an individual.
4379                        if($u->getId() == $r["required"])
4380                            return M_READ;
4381                        break;
4382                    case 1: // Reviewer is a group.
4383                        $required = $dms->getGroup($r["required"]);
4384                        if (is_object($required) && $required->isMember($u))
4385                            return M_READ;
4386                        break;
4387                    }
4388            }
4389            break;
4390        case S_RELEASED:
4391            break;
4392        case S_IN_WORKFLOW:
4393            if(!$this->_workflow)
4394                $this->getWorkflow();
4395
4396            if($this->_workflow) {
4397                if (!$this->_workflowState)
4398                    $this->getWorkflowState();
4399                $transitions = $this->_workflow['workflow']->getNextTransitions($this->_workflowState);
4400                foreach($transitions as $transition) {
4401                    if($this->triggerWorkflowTransitionIsAllowed($u, $transition))
4402                        return M_READ;
4403                }
4404            }
4405            break;
4406        case S_IN_REVISION:
4407            $status = $this->getRevisionStatus();
4408            foreach ($status as $r) {
4409                if($r['status'] != -2) // Check if reviewer was removed
4410                    switch ($r["type"]) {
4411                    case 0: // Revisor is an individual.
4412                        if($u->getId() == $r["required"])
4413                            return M_READ;
4414                        break;
4415                    case 1: // Revisor is a group.
4416                        $required = $dms->getGroup($r["required"]);
4417                        if (is_object($required) && $required->isMember($u))
4418                            return M_READ;
4419                        break;
4420                    }
4421            }
4422            break;
4423        case S_REJECTED:
4424            break;
4425        case S_OBSOLETE:
4426            break;
4427        case S_EXPIRED:
4428            break;
4429        }
4430
4431        return M_NONE;
4432    } /* }}} */
4433
4434    /**
4435     * Return a list of all reviewers separated by individuals and groups
4436     * This list will not take the review log into account. Therefore it
4437     * can contain reviewers which has actually been deleted as a reviewer.
4438     *
4439     * @return array|bool|null
4440     */
4441    public function getReviewers() { /* {{{ */
4442        $dms = $this->_document->getDMS();
4443        $db = $dms->getDB();
4444
4445        $queryStr=
4446            "SELECT * FROM `tblDocumentReviewers` WHERE `version`='".$this->_version
4447            ."' AND `documentID` = '". $this->_document->getID() ."' ";
4448
4449        $recs = $db->getResultArray($queryStr);
4450        if (is_bool($recs))
4451            return false;
4452        $reviewers = array('i'=>array(), 'g'=>array());
4453        foreach($recs as $rec) {
4454            if($rec['type'] == 0) {
4455                if($u = $dms->getUser($rec['required']))
4456                    $reviewers['i'][] = $u;
4457            } elseif($rec['type'] == 1) {
4458                if($g = $dms->getGroup($rec['required']))
4459                    $reviewers['g'][] = $g;
4460            }
4461        }
4462        return $reviewers;
4463    } /* }}} */
4464
4465    /**
4466     * Get the current review status of the document content
4467     * The review status is a list of reviewers and its current status
4468     *
4469     * @param integer $limit the number of recent status changes per reviewer
4470     * @return array list of review status
4471     */
4472    public function getReviewStatus($limit=1) { /* {{{ */
4473        $db = $this->_document->getDMS()->getDB();
4474
4475        if (!is_numeric($limit)) return false;
4476
4477        // Retrieve the current status of each assigned reviewer for the content
4478        // represented by this object.
4479        // FIXME: caching was turned off to make list of review log in ViewDocument
4480        // possible
4481        if (1 || !isset($this->_reviewStatus)) {
4482            /* First get a list of all reviews for this document content */
4483            $queryStr=
4484                "SELECT `reviewID` FROM `tblDocumentReviewers` WHERE `version`='".$this->_version
4485                ."' AND `documentID` = '". $this->_document->getID() ."' ";
4486            $recs = $db->getResultArray($queryStr);
4487            if (is_bool($recs) && !$recs)
4488                return false;
4489            $this->_reviewStatus = array();
4490            if($recs) {
4491                foreach($recs as $rec) {
4492                    $queryStr=
4493                        "SELECT `tblDocumentReviewers`.*, `tblDocumentReviewLog`.`reviewLogID`, `tblDocumentReviewLog`.`status`, ".
4494                        "`tblDocumentReviewLog`.`comment`, `tblDocumentReviewLog`.`date`, ".
4495                        "`tblDocumentReviewLog`.`userID`, `tblUsers`.`fullName`, `tblGroups`.`name` AS `groupName` ".
4496                        "FROM `tblDocumentReviewers` ".
4497                        "LEFT JOIN `tblDocumentReviewLog` USING (`reviewID`) ".
4498                        "LEFT JOIN `tblUsers` on `tblUsers`.`id` = `tblDocumentReviewers`.`required`".
4499                        "LEFT JOIN `tblGroups` on `tblGroups`.`id` = `tblDocumentReviewers`.`required`".
4500                        "WHERE `tblDocumentReviewers`.`reviewID` = '". $rec['reviewID'] ."' ".
4501                        "ORDER BY `tblDocumentReviewLog`.`reviewLogID` DESC LIMIT ".(int) $limit;
4502
4503                    $res = $db->getResultArray($queryStr);
4504                    if (is_bool($res) && !$res) {
4505                        unset($this->_reviewStatus);
4506                        return false;
4507                    }
4508                    foreach($res as &$t) {
4509                        $filename = $this->_dms->contentDir . $this->_document->getDir().'r'.$t['reviewLogID'];
4510                        if(SeedDMS_Core_File::file_exists($filename))
4511                            $t['file'] = $filename;
4512                        else
4513                            $t['file'] = '';
4514                    }
4515                    $this->_reviewStatus = array_merge($this->_reviewStatus, $res);
4516                }
4517            }
4518        }
4519        return $this->_reviewStatus;
4520    } /* }}} */
4521
4522    /**
4523     * Get the latest entries from the review log of the document content
4524     *
4525     * @param integer $limit the number of log entries returned, defaults to 1
4526     * @return array list of review log entries
4527     */
4528    public function getReviewLog($limit=1) { /* {{{ */
4529        $db = $this->_document->getDMS()->getDB();
4530
4531        if (!is_numeric($limit)) return false;
4532
4533        $queryStr=
4534            "SELECT * FROM `tblDocumentReviewLog` LEFT JOIN `tblDocumentReviewers` ON  `tblDocumentReviewLog`.`reviewID` = `tblDocumentReviewers`.`reviewID` WHERE `version`='".$this->_version
4535            ."' AND `documentID` = '". $this->_document->getID() ."' "
4536            ."ORDER BY `tblDocumentReviewLog`.`reviewLogID` DESC LIMIT ".(int) $limit;
4537        $recs = $db->getResultArray($queryStr);
4538        if (is_bool($recs) && !$recs)
4539            return false;
4540        return($recs);
4541    } /* }}} */
4542
4543    /**
4544     * Rewrites the complete review log
4545     *
4546     * Attention: this function is highly dangerous.
4547     * It removes an existing review log and rewrites it.
4548     * This method was added for importing an xml dump.
4549     *
4550     * @param array $reviewlog new status log with the newest log entry first.
4551     * @return boolean true on success, otherwise false
4552     */
4553    public function rewriteReviewLog($reviewers) { /* {{{ */
4554        $db = $this->_document->getDMS()->getDB();
4555
4556        $queryStr= "SELECT `tblDocumentReviewers`.* FROM `tblDocumentReviewers` WHERE `tblDocumentReviewers`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentReviewers`.`version` = '". $this->_version ."' ";
4557        $res = $db->getResultArray($queryStr);
4558        if (is_bool($res) && !$res)
4559            return false;
4560
4561        $db->startTransaction();
4562
4563        if($res) {
4564            foreach($res as $review) {
4565                $reviewID = $review['reviewID'];
4566
4567                /* First, remove the old entries */
4568                $queryStr = "DELETE FROM `tblDocumentReviewLog` WHERE `reviewID`=".$reviewID;
4569                if (!$db->getResult($queryStr)) {
4570                    $db->rollbackTransaction();
4571                    return false;
4572                }
4573
4574                $queryStr = "DELETE FROM `tblDocumentReviewers` WHERE `reviewID`=".$reviewID;
4575                if (!$db->getResult($queryStr)) {
4576                    $db->rollbackTransaction();
4577                    return false;
4578                }
4579            }
4580        }
4581
4582        /* Second, insert the new entries */
4583        foreach($reviewers as $review) {
4584            $queryStr = "INSERT INTO `tblDocumentReviewers` (`documentID`, `version`, `type`, `required`) ".
4585                "VALUES ('".$this->_document->getID()."', '".$this->_version."', ".$review['type'] .", ".(is_object($review['required']) ? $review['required']->getID() : (int) $review['required']).")";
4586            if (!$db->getResult($queryStr)) {
4587                $db->rollbackTransaction();
4588                return false;
4589            }
4590            $reviewID = $db->getInsertID('tblDocumentReviewers', 'reviewID');
4591            $reviewlog = array_reverse($review['logs']);
4592            foreach($reviewlog as $log) {
4593                if(!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
4594                    $db->rollbackTransaction();
4595                    return false;
4596                }
4597                $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
4598                    "VALUES ('".$reviewID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".(is_object($log['user']) ? $log['user']->getID() : (int) $log['user']).")";
4599                if (!$db->getResult($queryStr)) {
4600                    $db->rollbackTransaction();
4601                    return false;
4602                }
4603                $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
4604                if(!empty($log['file'])) {
4605                    SeedDMS_Core_File::copyFile($log['file'], $this->_dms->contentDir . $this->_document->getDir() . 'r' . $reviewLogID);
4606                }
4607            }
4608        }
4609
4610        $db->commitTransaction();
4611        return true;
4612    } /* }}} */
4613
4614    /**
4615     * Return a list of all approvers separated by individuals and groups
4616     * This list will not take the approval log into account. Therefore it
4617     * can contain approvers which has actually been deleted as an approver.
4618     *
4619     * @return array|bool|null
4620     */
4621    public function getApprovers() { /* {{{ */
4622        $dms = $this->_document->getDMS();
4623        $db = $dms->getDB();
4624
4625        $queryStr=
4626            "SELECT * FROM `tblDocumentApprovers` WHERE `version`='".$this->_version
4627            ."' AND `documentID` = '". $this->_document->getID() ."' ";
4628
4629        $recs = $db->getResultArray($queryStr);
4630        if (is_bool($recs))
4631            return false;
4632        $approvers = array('i'=>array(), 'g'=>array());
4633        foreach($recs as $rec) {
4634            if($rec['type'] == 0) {
4635                if($u = $dms->getUser($rec['required']))
4636                    $approvers['i'][] = $u;
4637            } elseif($rec['type'] == 1) {
4638                if($g = $dms->getGroup($rec['required']))
4639                    $approvers['g'][] = $g;
4640            }
4641        }
4642        return $approvers;
4643    } /* }}} */
4644
4645    /**
4646     * Get the current approval status of the document content
4647     * The approval status is a list of approvers and its current status
4648     *
4649     * @param integer $limit the number of recent status changes per approver
4650     * @return array list of approval status
4651     */
4652    public function getApprovalStatus($limit=1) { /* {{{ */
4653        $db = $this->_document->getDMS()->getDB();
4654
4655        if (!is_numeric($limit)) return false;
4656
4657        // Retrieve the current status of each assigned approver for the content
4658        // represented by this object.
4659        // FIXME: caching was turned off to make list of approval log in ViewDocument
4660        // possible
4661        if (1 || !isset($this->_approvalStatus)) {
4662            /* First get a list of all approvals for this document content */
4663            $queryStr=
4664                "SELECT `approveID` FROM `tblDocumentApprovers` WHERE `version`='".$this->_version
4665                ."' AND `documentID` = '". $this->_document->getID() ."' ";
4666            $recs = $db->getResultArray($queryStr);
4667            if (is_bool($recs) && !$recs)
4668                return false;
4669            $this->_approvalStatus = array();
4670            if($recs) {
4671                foreach($recs as $rec) {
4672                    $queryStr=
4673                        "SELECT `tblDocumentApprovers`.*, `tblDocumentApproveLog`.`approveLogID`, `tblDocumentApproveLog`.`status`, ".
4674                        "`tblDocumentApproveLog`.`comment`, `tblDocumentApproveLog`.`date`, ".
4675                        "`tblDocumentApproveLog`.`userID`, `tblUsers`.`fullName`, `tblGroups`.`name` AS `groupName` ".
4676                        "FROM `tblDocumentApprovers` ".
4677                        "LEFT JOIN `tblDocumentApproveLog` USING (`approveID`) ".
4678                        "LEFT JOIN `tblUsers` on `tblUsers`.`id` = `tblDocumentApprovers`.`required` ".
4679                        "LEFT JOIN `tblGroups` on `tblGroups`.`id` = `tblDocumentApprovers`.`required`".
4680                        "WHERE `tblDocumentApprovers`.`approveID` = '". $rec['approveID'] ."' ".
4681                        "ORDER BY `tblDocumentApproveLog`.`approveLogID` DESC LIMIT ".(int) $limit;
4682
4683                    $res = $db->getResultArray($queryStr);
4684                    if (is_bool($res) && !$res) {
4685                        unset($this->_approvalStatus);
4686                        return false;
4687                    }
4688                    foreach($res as &$t) {
4689                        $filename = $this->_dms->contentDir . $this->_document->getDir().'a'.$t['approveLogID'];
4690                        if(SeedDMS_Core_File::file_exists($filename))
4691                            $t['file'] = $filename;
4692                        else
4693                            $t['file'] = '';
4694                    }
4695                    $this->_approvalStatus = array_merge($this->_approvalStatus, $res);
4696                }
4697            }
4698        }
4699        return $this->_approvalStatus;
4700    } /* }}} */
4701
4702    /**
4703     * Get the latest entries from the approval log of the document content
4704     *
4705     * @param integer $limit the number of log entries returned, defaults to 1
4706     * @return array list of approval log entries
4707     */
4708    public function getApproveLog($limit=1) { /* {{{ */
4709        $db = $this->_document->getDMS()->getDB();
4710
4711        if (!is_numeric($limit)) return false;
4712
4713        $queryStr=
4714            "SELECT * FROM `tblDocumentApproveLog` LEFT JOIN `tblDocumentApprovers` ON  `tblDocumentApproveLog`.`approveID` = `tblDocumentApprovers`.`approveID` WHERE `version`='".$this->_version
4715            ."' AND `documentID` = '". $this->_document->getID() ."' "
4716            ."ORDER BY `tblDocumentApproveLog`.`approveLogID` DESC LIMIT ".(int) $limit;
4717        $recs = $db->getResultArray($queryStr);
4718        if (is_bool($recs) && !$recs)
4719            return false;
4720        return($recs);
4721    } /* }}} */
4722
4723    /**
4724     * Rewrites the complete approval log
4725     *
4726     * Attention: this function is highly dangerous.
4727     * It removes an existing review log and rewrites it.
4728     * This method was added for importing an xml dump.
4729     *
4730     * @param array $reviewlog new status log with the newest log entry first.
4731     * @return boolean true on success, otherwise false
4732     */
4733    public function rewriteApprovalLog($reviewers) { /* {{{ */
4734        $db = $this->_document->getDMS()->getDB();
4735
4736        $queryStr= "SELECT `tblDocumentApprovers`.* FROM `tblDocumentApprovers` WHERE `tblDocumentApprovers`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentApprovers`.`version` = '". $this->_version ."' ";
4737        $res = $db->getResultArray($queryStr);
4738        if (is_bool($res) && !$res)
4739            return false;
4740
4741        $db->startTransaction();
4742
4743        if($res) {
4744            foreach($res as $review) {
4745                $reviewID = $review['reviewID'];
4746
4747                /* First, remove the old entries */
4748                $queryStr = "DELETE FROM `tblDocumentApproveLog` WHERE `approveID`=".$reviewID;
4749                if (!$db->getResult($queryStr)) {
4750                    $db->rollbackTransaction();
4751                    return false;
4752                }
4753
4754                $queryStr = "DELETE FROM `tblDocumentApprovers` WHERE `approveID`=".$reviewID;
4755                if (!$db->getResult($queryStr)) {
4756                    $db->rollbackTransaction();
4757                    return false;
4758                }
4759            }
4760        }
4761
4762        /* Second, insert the new entries */
4763        foreach($reviewers as $review) {
4764            $queryStr = "INSERT INTO `tblDocumentApprovers` (`documentID`, `version`, `type`, `required`) ".
4765                "VALUES ('".$this->_document->getID()."', '".$this->_version."', ".$review['type'] .", ".(is_object($review['required']) ? $review['required']->getID() : (int) $review['required']).")";
4766            if (!$db->getResult($queryStr)) {
4767                $db->rollbackTransaction();
4768                return false;
4769            }
4770            $reviewID = $db->getInsertID('tblDocumentApprovers', 'approveID');
4771            $reviewlog = array_reverse($review['logs']);
4772            foreach($reviewlog as $log) {
4773                if(!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
4774                    $db->rollbackTransaction();
4775                    return false;
4776                }
4777                $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
4778                    "VALUES ('".$reviewID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".(is_object($log['user']) ? $log['user']->getID() : (int) $log['user']).")";
4779                if (!$db->getResult($queryStr)) {
4780                    $db->rollbackTransaction();
4781                    return false;
4782                }
4783                $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
4784                if(!empty($log['file'])) {
4785                    SeedDMS_Core_File::copyFile($log['file'], $this->_dms->contentDir . $this->_document->getDir() . 'a' . $approveLogID);
4786                }
4787            }
4788        }
4789
4790        $db->commitTransaction();
4791        return true;
4792    } /* }}} */
4793
4794    /**
4795     * Return a list of all recipients separated by individuals and groups
4796     * This list will not take the receipt log into account. Therefore it
4797     * can contain recipients which has actually been deleted as a recipient.
4798     *
4799     * @return array|bool|null
4800     */
4801    function getRecipients() { /* {{{ */
4802        $dms = $this->_document->getDMS();
4803        $db = $dms->getDB();
4804
4805        $queryStr=
4806            "SELECT * FROM `tblDocumentRecipients` WHERE `version`='".$this->_version
4807            ."' AND `documentID` = '". $this->_document->getID() ."' ";
4808
4809        $recs = $db->getResultArray($queryStr);
4810        if (is_bool($recs))
4811            return false;
4812        $recipients = array('i'=>array(), 'g'=>array());
4813        foreach($recs as $rec) {
4814            if($rec['type'] == 0) {
4815                if($u = $dms->getUser($rec['required']))
4816                    $recipients['i'][] = $u;
4817            } elseif($rec['type'] == 1) {
4818                if($g = $dms->getGroup($rec['required']))
4819                    $recipients['g'][] = $g;
4820            }
4821        }
4822        return $recipients;
4823    } /* }}} */
4824
4825    /**
4826     * Get the current receipt status of the document content
4827     * The receipt status is a list of receipts
4828     *
4829     * @param integer $limit maximum number of status changes per receiver
4830     * @return array list of receipts
4831     */
4832    function getReceiptStatus($limit=1) { /* {{{ */
4833        $db = $this->_document->getDMS()->getDB();
4834
4835        if (!is_numeric($limit)) return false;
4836
4837        // Retrieve the current status of each assigned reviewer for the content
4838        // represented by this object.
4839        // When just the last log entry for each recipient is needed then a single
4840        // sql statement is much faster than the code below which first retrieves
4841        // all receivers and than the logs
4842        // FIXME: caching was turned off to make list of review log in ViewDocument
4843        // possible
4844        if($limit == 1) {
4845            /* The following sql statement is somewhat optimized. The first join is
4846             * crucial because it should first take the table with the least number
4847             * of records and join the other tables. ttreceiptid join tblDocumentRecipients
4848             * is faster than tblDocumentRecipients join ttreceiptid
4849             */
4850            if (!$db->createTemporaryTable("ttreceiptid")) {
4851                return false;
4852            }
4853            $queryStr=
4854                "SELECT `tblDocumentRecipients`.*, `tblDocumentReceiptLog`.`receiptLogID`, `tblDocumentReceiptLog`.`status`, `tblDocumentReceiptLog`.`comment`, `tblDocumentReceiptLog`.`date`, `tblDocumentReceiptLog`.`userID`, `tblUsers`.`fullName`, `tblGroups`.`name` FROM `ttreceiptid` LEFT JOIN `tblDocumentRecipients` ON `tblDocumentRecipients`.`receiptID`=`ttreceiptid`.`receiptID` LEFT JOIN `tblDocumentReceiptLog` ON `ttreceiptid`.`maxLogID`=`tblDocumentReceiptLog`.`receiptLogID` LEFT JOIN `tblUsers` ON `tblDocumentRecipients`.`required`=`tblUsers`.`id` LEFT JOIN `tblGroups` ON `tblDocumentRecipients`.`required`=`tblGroups`.`id` WHERE `version`='".$this->_version
4855                ."' AND `documentID` = '". $this->_document->getID() ."' ";
4856            $recs = $db->getResultArray($queryStr);
4857            if (is_bool($recs) && !$recs) {
4858                unset($this->_receiptStatus);
4859                return false;
4860            }
4861            $this->_receiptStatus = $recs;
4862        } elseif (1 || !isset($this->_receiptStatus)) {
4863            /* First get a list of all receipts for this document content */
4864            $queryStr=
4865                "SELECT `receiptID` FROM `tblDocumentRecipients` WHERE `version`='".$this->_version
4866                ."' AND `documentID` = '". $this->_document->getID() ."' ";
4867            $recs = $db->getResultArray($queryStr);
4868            if (is_bool($recs) && !$recs)
4869                return false;
4870            $this->_receiptStatus = array();
4871            if($recs) {
4872                foreach($recs as $rec) {
4873                    $queryStr=
4874                        "SELECT `tblDocumentRecipients`.*, `tblDocumentReceiptLog`.`receiptLogID`, ".
4875                        "`tblDocumentReceiptLog`.`status`, ".
4876                        "`tblDocumentReceiptLog`.`comment`, ".
4877                        "`tblDocumentReceiptLog`.`date`, ".
4878                        "`tblDocumentReceiptLog`.`userID`, `tblUsers`.`fullName`, `tblGroups`.`name` AS `groupName` ".
4879                        "FROM `tblDocumentRecipients` ".
4880                        "LEFT JOIN `tblDocumentReceiptLog` USING (`receiptID`) ".
4881                        "LEFT JOIN `tblUsers` on `tblUsers`.`id` = `tblDocumentRecipients`.`required` ".
4882                        "LEFT JOIN `tblGroups` on `tblGroups`.`id` = `tblDocumentRecipients`.`required` ".
4883                        "WHERE `tblDocumentRecipients`.`receiptID` = '". $rec['receiptID'] ."' ".
4884                        "ORDER BY `tblDocumentReceiptLog`.`receiptLogID` DESC LIMIT ".(int) $limit;
4885
4886                    $res = $db->getResultArray($queryStr);
4887                    if (is_bool($res) && !$res) {
4888                        unset($this->_receiptStatus);
4889                        return false;
4890                    }
4891                    $this->_receiptStatus = array_merge($this->_receiptStatus, $res);
4892                }
4893            }
4894        }
4895        return $this->_receiptStatus;
4896    } /* }}} */
4897
4898    /**
4899     * Get the latest entries from the receipt log of the document content
4900     *
4901     * @param integer $limit the number of log entries returned, defaults to 1
4902     * @return array list of receiptlog entries
4903     */
4904    function getReceiptLog($limit=1) { /* {{{ */
4905        $db = $this->_document->getDMS()->getDB();
4906
4907        if (!is_numeric($limit)) return false;
4908
4909        $queryStr=
4910            "SELECT * FROM `tblDocumentReceiptLog` LEFT JOIN `tblDocumentRecipients` ON  `tblDocumentReceiptLog`.`receiptID` = `tblDocumentRecipients`.`receiptID` WHERE `version`='".$this->_version
4911            ."' AND `documentID` = '". $this->_document->getID() ."' "
4912            ."ORDER BY `tblDocumentReceiptLog`.`receiptLogID` DESC LIMIT ".(int) $limit;
4913        $recs = $db->getResultArray($queryStr);
4914        if (is_bool($recs) && !$recs)
4915            return false;
4916        return($recs);
4917    } /* }}} */
4918
4919    /**
4920     * Rewrites the complete receipt log
4921     * 
4922     * Attention: this function is highly dangerous.
4923     * It removes an existing receipt log and rewrites it.
4924     * This method was added for importing an xml dump.
4925     *
4926     * @param array $receiptlog new status log with the newest log entry first.
4927     * @return boolean true on success, otherwise false
4928     */
4929    function rewriteReceiptLog($recipients) { /* {{{ */
4930        $db = $this->_document->getDMS()->getDB();
4931
4932        $queryStr= "SELECT `tblDocumentRecipients`.* FROM `tblDocumentRecipients` WHERE `tblDocumentRecipients`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentRecipients`.`version` = '". $this->_version ."' ";
4933        $res = $db->getResultArray($queryStr);
4934        if (is_bool($res) && !$res)
4935            return false;
4936
4937        $db->startTransaction();
4938
4939        if($res) {
4940            foreach($res as $receipt) {
4941                $receiptID = $receipt['receiptID'];
4942
4943                /* First, remove the old entries */
4944                $queryStr = "DELETE from `tblDocumentReceiptLog` where `receiptID`=".$receiptID;
4945                if (!$db->getResult($queryStr)) {
4946                    $db->rollbackTransaction();
4947                    return false;
4948                }
4949
4950                $queryStr = "DELETE from `tblDocumentRecipients` where `receiptID`=".$receiptID;
4951                if (!$db->getResult($queryStr)) {
4952                    $db->rollbackTransaction();
4953                    return false;
4954                }
4955            }
4956        }
4957
4958        /* Second, insert the new entries */
4959        foreach($recipients as $receipt) {
4960            $queryStr = "INSERT INTO `tblDocumentRecipients` (`documentID`, `version`, `type`, `required`) ".
4961                "VALUES ('".$this->_document->getID()."', '".$this->_version."', ".$receipt['type'] .", ".(is_object($receipt['required']) ? $receipt['required']->getID() : (int) $receipt['required']).")";
4962            if (!$db->getResult($queryStr)) {
4963                $db->rollbackTransaction();
4964                return false;
4965            }
4966            $receiptID = $db->getInsertID('tblDocumentRecipients', 'receiptID');
4967            $receiptlog = array_reverse($receipt['logs']);
4968            foreach($receiptlog as $log) {
4969                if(!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
4970                    $db->rollbackTransaction();
4971                    return false;
4972                }
4973                $queryStr = "INSERT INTO `tblDocumentReceiptLog` (`receiptID`, `status`, `comment`, `date`, `userID`) ".
4974                    "VALUES ('".$receiptID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".(is_object($log['user']) ? $log['user']->getID() : (int) $log['user']).")";
4975                if (!$db->getResult($queryStr)) {
4976                    $db->rollbackTransaction();
4977                    return false;
4978                }
4979                $receiptLogID = $db->getInsertID('tblDocumentReceiptLog', 'receiptLogID');
4980                if(!empty($log['file'])) {
4981                    SeedDMS_Core_File::copyFile($log['file'], $this->_dms->contentDir . $this->_document->getDir() . 'r' . $receiptLogID);
4982                }
4983            }
4984        }
4985
4986        $db->commitTransaction();
4987        return true;
4988    } /* }}} */
4989
4990    /**
4991     * Return a list of all revisors separated by individuals and groups
4992     * This list will not take the revision log into account. Therefore it
4993     * can contain revisors which has actually been deleted as a revisor.
4994     *
4995     * @return array|bool|null
4996     */
4997    function getRevisors() { /* {{{ */
4998        $dms = $this->_document->getDMS();
4999        $db = $dms->getDB();
5000
5001        $queryStr=
5002            "SELECT * FROM `tblDocumentRevisors` WHERE `version`='".$this->_version
5003            ."' AND `documentID` = '". $this->_document->getID() ."' ";
5004
5005        $recs = $db->getResultArray($queryStr);
5006        if (is_bool($recs))
5007            return false;
5008        $revisors = array('i'=>array(), 'g'=>array());
5009        foreach($recs as $rec) {
5010            if($rec['type'] == 0) {
5011                if($u = $dms->getUser($rec['required']))
5012                    $revisors['i'][] = $u;
5013            } elseif($rec['type'] == 1) {
5014                if($g = $dms->getGroup($rec['required']))
5015                    $revisors['g'][] = $g;
5016            }
5017        }
5018        return $revisors;
5019    } /* }}} */
5020
5021    /**
5022     * Get the current revision status of the document content
5023     * The revision status is a list of revisions
5024     * If $limit is 1 it will return just the last log entry for each
5025     * revisor.
5026     * Keep in mind that a revision log may contain repeating revisions.
5027     *
5028     * @param integer $limit maximum number of records per revisor
5029     * @return array list of revisions
5030     */
5031    function getRevisionStatus($limit=1) { /* {{{ */
5032        $db = $this->_document->getDMS()->getDB();
5033
5034        if (!is_numeric($limit)) return false;
5035
5036        // Retrieve the current status of each assigned reviewer for the content
5037        // represented by this object.
5038        // FIXME: caching was turned off to make list of review log in ViewDocument
5039        // possible
5040        if (1 || !isset($this->_revisionStatus)) {
5041            /* First get a list of all revisions for this document content */
5042            $queryStr=
5043                "SELECT `revisionID` FROM `tblDocumentRevisors` WHERE `version`='".$this->_version
5044                ."' AND `documentID` = '". $this->_document->getID() ."' ";
5045            $recs = $db->getResultArray($queryStr);
5046            if (is_bool($recs) && !$recs)
5047                return false;
5048            $this->_revisionStatus = array();
5049            if($recs) {
5050                foreach($recs as $rec) {
5051                    $queryStr=
5052                        "SELECT `tblDocumentRevisors`.*, `tblDocumentRevisionLog`.`revisionLogID`, ".
5053                        "`tblDocumentRevisionLog`.`status`, ".
5054                        "`tblDocumentRevisionLog`.`comment`, ".
5055                        "`tblDocumentRevisionLog`.`date`, ".
5056                        "`tblDocumentRevisionLog`.`userID`, `tblUsers`.`fullName`, `tblGroups`.`name` AS `groupName` ".
5057                        "FROM `tblDocumentRevisors` ".
5058                        "LEFT JOIN `tblDocumentRevisionLog` USING (`revisionID`) ".
5059                        "LEFT JOIN `tblUsers` on `tblUsers`.`id` = `tblDocumentRevisors`.`required` ".
5060                        "LEFT JOIN `tblGroups` on `tblGroups`.`id` = `tblDocumentRevisors`.`required` ".
5061                        "WHERE `tblDocumentRevisors`.`revisionID` = '". $rec['revisionID'] ."' ".
5062                        "ORDER BY `tblDocumentRevisionLog`.`revisionLogID` DESC LIMIT ".(int) $limit;
5063
5064                    $res = $db->getResultArray($queryStr);
5065                    if (is_bool($res) && !$res) {
5066                        unset($this->_revisionStatus);
5067                        return false;
5068                    }
5069                    $this->_revisionStatus = array_merge($this->_revisionStatus, $res);
5070                }
5071            }
5072        }
5073        return $this->_revisionStatus;
5074    } /* }}} */
5075
5076    /**
5077     * Get the latest entries from the revision log of the document content
5078     *
5079     * @param integer $limit the number of log entries returned, defaults to 1
5080     * @return array list of revisionlog entries
5081     */
5082    function getRevisionLog($limit=1) { /* {{{ */
5083        $db = $this->_document->getDMS()->getDB();
5084
5085        if (!is_numeric($limit)) return false;
5086
5087        $queryStr=
5088            "SELECT * FROM `tblDocumentRevisionLog` LEFT JOIN `tblDocumentRevisors` ON  `tblDocumentRevisionLog`.`revisionID` = `tblDocumentRevisors`.`revisionID` WHERE `version`='".$this->_version
5089            ."' AND `documentID` = '". $this->_document->getID() ."' "
5090            ."ORDER BY `tblDocumentRevisionLog`.`revisionLogID` DESC LIMIT ".(int) $limit;
5091        $recs = $db->getResultArray($queryStr);
5092        if (is_bool($recs) && !$recs)
5093            return false;
5094        return($recs);
5095    } /* }}} */
5096
5097    /**
5098     * Rewrites the complete revision log
5099     * 
5100     * Attention: this function is highly dangerous.
5101     * It removes an existing revision log and rewrites it.
5102     * This method was added for importing an xml dump.
5103     *
5104     * @param array $revisionlog new status log with the newest log entry first.
5105     * @return boolean 0 on success, otherwise a negativ error number
5106     */
5107    function rewriteRevisionLog($revisions) { /* {{{ */
5108        $db = $this->_document->getDMS()->getDB();
5109
5110        $queryStr= "SELECT `tblDocumentRevisors`.* FROM `tblDocumentRevisors` WHERE `tblDocumentRevisors`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentRevisors`.`version` = '". $this->_version ."' ";
5111        $res = $db->getResultArray($queryStr);
5112        if (is_bool($res) && !$res)
5113            return false;
5114
5115        $db->startTransaction();
5116
5117        if($res) {
5118            foreach($res as $revision) {
5119                $revisionID = $revision['revisionID'];
5120
5121                /* First, remove the old entries */
5122                $queryStr = "DELETE from `tblDocumentRevisionLog` where `revisionID`=".$revisionID;
5123                if (!$db->getResult($queryStr)) {
5124                    $db->rollbackTransaction();
5125                    return false;
5126                }
5127
5128                $queryStr = "DELETE from `tblDocumentRevisors` where `revisionID`=".$revisionID;
5129                if (!$db->getResult($queryStr)) {
5130                    $db->rollbackTransaction();
5131                    return false;
5132                }
5133            }
5134        }
5135
5136        /* Second, insert the new entries */
5137        foreach($revisions as $revision) {
5138            $queryStr = "INSERT INTO `tblDocumentRevisors` (`documentID`, `version`, `type`, `required`) ".
5139                "VALUES ('".$this->_document->getID()."', '".$this->_version."', ".$revision['type'] .", ".(is_object($revision['required']) ? $revision['required']->getID() : (int) $revision['required']).")";
5140            if (!$db->getResult($queryStr)) {
5141                $db->rollbackTransaction();
5142                return false;
5143            }
5144            $revisionID = $db->getInsertID('tblDocumentRevisors', 'revisionID');
5145            $revisionlog = array_reverse($revision['logs']);
5146            foreach($revisionlog as $log) {
5147                if(!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
5148                    $db->rollbackTransaction();
5149                    return false;
5150                }
5151                $queryStr = "INSERT INTO `tblDocumentRevisionLog` (`revisionID`, `status`, `comment`, `date`, `userID`) ".
5152                    "VALUES ('".$revisionID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".(is_object($log['user']) ? $log['user']->getID() : (int) $log['user']).")";
5153                if (!$db->getResult($queryStr)) {
5154                    $db->rollbackTransaction();
5155                    return false;
5156                }
5157                $revisionLogID = $db->getInsertID('tblDocumentRevisionLog', 'revisionLogID');
5158                if(!empty($log['file'])) {
5159                    SeedDMS_Core_File::copyFile($log['file'], $this->_dms->contentDir . $this->_document->getDir() . 'r' . $revisionLogID);
5160                }
5161            }
5162        }
5163
5164        $db->commitTransaction();
5165        return true;
5166    } /* }}} */
5167
5168    /**
5169     * Check if document version has a scheduled revision workflow.
5170     * The method will update the document status log database table
5171     * if needed and set the revisiondate of the content to $next.
5172     *
5173     * FIXME: This method does not check if there are any revisors left. Even
5174     * if all revisors have been removed, it will still start the revision workflow!
5175     * NOTE: This seems not the case anymore. The status of each revision is
5176     * checked. Only if at least one status is S_LOG_SLEEPING the revision will be
5177     * started. This wouldn't be the case if all revisors had been removed.
5178     *
5179     * @param object $user user requesting the possible automatic change
5180     * @param string $next next date for review
5181     * @return boolean true if status has changed
5182     */
5183    function checkForDueRevisionWorkflow($user, $next=''){ /* {{{ */
5184        $st=$this->getStatus();
5185
5186        /* A revision workflow will only be started if the document version is released */
5187        if($st["status"] == S_RELEASED) {
5188            /* First check if there are any scheduled revisions currently sleeping */
5189            $pendingRevision=false;
5190            unset($this->_revisionStatus);  // force to be reloaded from DB
5191            $revisionStatus=$this->getRevisionStatus();
5192            if (is_array($revisionStatus) && count($revisionStatus)>0) {
5193                foreach ($revisionStatus as $a){
5194                    if ($a["status"]==S_LOG_SLEEPING || $a["status"]==S_LOG_SLEEPING){
5195                        $pendingRevision=true;
5196                        break;
5197                    }
5198                }
5199            }
5200            if(!$pendingRevision)
5201                return false;
5202
5203            /* We have sleeping revision, next check if the revision is already due */
5204            if($this->getRevisionDate() && $this->getRevisionDate() <= date('Y-m-d 00:00:00')) {
5205                if($this->startRevision($user, 'Automatic start of revision workflow scheduled for '.$this->getRevisionDate())) {
5206                    if($next) {
5207                        $tmp = explode('-', substr($next, 0, 10));
5208                        if(checkdate($tmp[1], $tmp[2], $tmp[0]))
5209                            $this->setRevisionDate($next);
5210                    } else {
5211                        $this->setRevisionDate(false);
5212                    }
5213                    return true;
5214                }
5215            }
5216        }
5217        return false;
5218    } /* }}} */
5219
5220    /**
5221     * Add user as new reviewer
5222     *
5223     * @param object $user user in charge for the review
5224     * @param object $requestUser user requesting the operation (usually the
5225     * currently logged in user)
5226     *
5227     * @return integer|false if > 0 the id of the review log, if < 0 the error
5228     * code, false in case of an sql error
5229     */
5230    public function addIndReviewer($user, $requestUser) { /* {{{ */
5231        if(!$user || !$requestUser)
5232            return -1;
5233
5234        $db = $this->_document->getDMS()->getDB();
5235
5236        if(!$user->isType('user'))
5237            return -1;
5238
5239        $userID = $user->getID();
5240
5241        // Get the list of users and groups with read access to this document.
5242        if($this->_document->getAccessMode($user) < M_READ) {
5243            return -2;
5244        }
5245
5246        // Check to see if the user has already been added to the review list.
5247        $reviewStatus = $user->getReviewStatus($this->_document->getID(), $this->_version);
5248        if (is_bool($reviewStatus) && !$reviewStatus) {
5249            return false;
5250        }
5251        $indstatus = false;
5252        if (count($reviewStatus["indstatus"]) > 0) {
5253            $indstatus = array_pop($reviewStatus["indstatus"]);
5254            if($indstatus["status"]!=-2) {
5255                // User is already on the list of reviewers; return an error.
5256                return -3;
5257            }
5258        }
5259
5260        // Add the user into the review database.
5261        if (!$indstatus || ($indstatus && $indstatus["status"]!=-2)) {
5262            $queryStr = "INSERT INTO `tblDocumentReviewers` (`documentID`, `version`, `type`, `required`) ".
5263                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '0', '". $userID ."')";
5264            $res = $db->getResult($queryStr);
5265            if (is_bool($res) && !$res) {
5266                return false;
5267            }
5268            $reviewID = $db->getInsertID('tblDocumentReviewers', 'reviewID');
5269        }
5270        else {
5271            $reviewID = isset($indstatus["reviewID"]) ? $indstatus["reviewID"] : NULL;
5272        }
5273
5274        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
5275            "VALUES ('". $reviewID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5276        $res = $db->getResult($queryStr);
5277        if (is_bool($res) && !$res) {
5278            return false;
5279        }
5280
5281        $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
5282        $db->dropTemporaryTable('ttreviewid');
5283        return $reviewLogID;
5284    } /* }}} */
5285
5286    /**
5287     * Add group as new reviewer
5288     *
5289     * @param object $group group in charge for the review
5290     * @param object $requestUser user requesting the operation (usually the
5291     * currently logged in user)
5292     *
5293     * @return integer|false if > 0 the id of the review log, if < 0 the error
5294     * code, false in case of an sql error
5295     */
5296    public function addGrpReviewer($group, $requestUser) { /* {{{ */
5297        if(!$group || !$requestUser)
5298            return -1;
5299
5300        $db = $this->_document->getDMS()->getDB();
5301
5302        if(!$group->isType('group'))
5303            return -1;
5304
5305        $groupID = $group->getID();
5306
5307        // Get the list of users and groups with read access to this document.
5308        if (!isset($this->_readAccessList)) {
5309            // TODO: error checking.
5310            $this->_readAccessList = $this->_document->getReadAccessList();
5311        }
5312        $approved = false;
5313        foreach ($this->_readAccessList["groups"] as $appGroup) {
5314            if ($groupID == $appGroup->getID()) {
5315                $approved = true;
5316                break;
5317            }
5318        }
5319        if (!$approved) {
5320            return -2;
5321        }
5322
5323        // Check to see if the group has already been added to the review list.
5324        $reviewStatus = $group->getReviewStatus($this->_document->getID(), $this->_version);
5325        if (is_bool($reviewStatus) && !$reviewStatus) {
5326            return false;
5327        }
5328        if (count($reviewStatus) > 0 && $reviewStatus[0]["status"]!=-2) {
5329            // Group is already on the list of reviewers; return an error.
5330            return -3;
5331        }
5332
5333        // Add the group into the review database.
5334        if (!isset($reviewStatus[0]["status"]) || (isset($reviewStatus[0]["status"]) && $reviewStatus[0]["status"]!=-2)) {
5335            $queryStr = "INSERT INTO `tblDocumentReviewers` (`documentID`, `version`, `type`, `required`) ".
5336                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '1', '". $groupID ."')";
5337            $res = $db->getResult($queryStr);
5338            if (is_bool($res) && !$res) {
5339                return false;
5340            }
5341            $reviewID = $db->getInsertID('tblDocumentReviewers', 'reviewID');
5342        }
5343        else {
5344            $reviewID = isset($reviewStatus[0]["reviewID"])?$reviewStatus[0]["reviewID"]:NULL;
5345        }
5346
5347        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
5348            "VALUES ('". $reviewID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5349        $res = $db->getResult($queryStr);
5350        if (is_bool($res) && !$res) {
5351            return false;
5352        }
5353
5354        $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
5355        $db->dropTemporaryTable('ttreviewid');
5356        return $reviewLogID;
5357    } /* }}} */
5358
5359    /**
5360     * Add a review to the document content
5361     *
5362     * This method will add an entry to the table tblDocumentReviewLog.
5363     * It will first check if the user is ment to review the document version.
5364     * It not the return value is -3.
5365     * Next it will check if the users has been removed from the list of
5366     * reviewers. In that case -4 will be returned.
5367     * If the given review status has been set by the user before, it cannot
5368     * be set again and 0 will be returned. Ð†f the review could be succesfully
5369     * added, the review log id will be returned.
5370     *
5371     * @see SeedDMS_Core_DocumentContent::setApprovalByInd()
5372     *
5373     * @param object  $user user doing the review
5374     * @param object  $requestUser user asking for the review, this is mostly
5375     * the user currently logged in.
5376     * @param integer $status status of review
5377     * @param string  $comment comment for review
5378     *
5379     * @return integer|bool new review log id, error code 0 till -4,
5380     * false in case of an sql error
5381     */
5382    public function setReviewByInd($user, $requestUser, $status, $comment, $file='') { /* {{{ */
5383        if(!$user || !$requestUser)
5384            return -1;
5385
5386        $db = $this->_document->getDMS()->getDB();
5387
5388        if(!$user->isType('user'))
5389            return -1;
5390
5391        // Check if the user is on the review list at all.
5392        $reviewStatus = $user->getReviewStatus($this->_document->getID(), $this->_version);
5393        if (is_bool($reviewStatus) && !$reviewStatus) {
5394            return false;
5395        }
5396        if (count($reviewStatus["indstatus"])==0) {
5397            // User is not assigned to review this document. No action required.
5398            // Return an error.
5399            return -3;
5400        }
5401        $indstatus = array_pop($reviewStatus["indstatus"]);
5402        if ($indstatus["status"]==-2) {
5403            // User has been deleted from reviewers
5404            return -4;
5405        }
5406        // Check if the status is really different from the current status
5407        if ($indstatus["status"] == $status)
5408            return 0;
5409
5410        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`,
5411            `comment`, `date`, `userID`) ".
5412            "VALUES ('". $indstatus["reviewID"] ."', '".
5413            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
5414            $requestUser->getID() ."')";
5415        $res=$db->getResult($queryStr);
5416        if (is_bool($res) && !$res)
5417            return false;
5418
5419        $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
5420        if($file) {
5421            SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'r' . $reviewLogID);
5422        }
5423        return $reviewLogID;
5424    } /* }}} */
5425
5426    /**
5427     * Add another entry to review log which resets the status
5428     *
5429     * This method will not delete anything from the database, but will add
5430     * a new review log entry which sets the status to 0. This is only allowed
5431     * if the current status is either 1 (reviewed) or -1 (rejected).
5432     *
5433     * After calling this method SeedDMS_Core_DocumentContent::verifyStatus()
5434     * should be called to recalculate the document status.
5435     *
5436     * @param integer $reviewid id of review
5437     * @param SeedDMS_Core_User $requestUser user requesting the removal
5438     * @param string $comment comment
5439     *
5440     * @return integer|bool true if successful, error code < 0,
5441     * false in case of an sql error
5442     */
5443    public function removeReview($reviewid, $requestUser, $comment='') { /* {{{ */
5444        $db = $this->_document->getDMS()->getDB();
5445
5446        // Check to see if the user can be removed from the review list.
5447        $reviews = $this->getReviewStatus();
5448        if (is_bool($reviews) && !$reviews) {
5449            return false;
5450        }
5451        $reviewStatus = null;
5452        foreach($reviews as $review) {
5453            if($review['reviewID'] == $reviewid) {
5454                $reviewStatus = $review;
5455                break;
5456            }
5457        }
5458        if(!$reviewStatus)
5459            return -2;
5460
5461        // The review log entry may only be removed if the status is 1 or -1
5462        if ($reviewStatus["status"] != 1 && $reviewStatus["status"] != -1)
5463            return -3;
5464
5465        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`,
5466            `comment`, `date`, `userID`) ".
5467            "VALUES ('". $reviewStatus["reviewID"] ."', '0', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
5468            $requestUser->getID() ."')";
5469        $res=$db->getResult($queryStr);
5470        if (is_bool($res) && !$res)
5471            return false;
5472
5473        return true;
5474    } /* }}} */
5475
5476    /**
5477     * Add a review to the document content
5478     *
5479     * This method is similar to
5480     * {@see SeedDMS_Core_DocumentContent::setReviewByInd()} but adds a review
5481     * for a group instead of a user.
5482     *
5483     * @param object  $group group doing the review
5484     * @param object  $requestUser user asking for the review, this is mostly
5485     * the user currently logged in.
5486     * @param integer $status status of review
5487     * @param string  $comment comment for review
5488     *
5489     * @return integer|bool new review log id, error code 0 till -4,
5490     * false in case of an sql error
5491     */
5492    public function setReviewByGrp($group, $requestUser, $status, $comment, $file='') { /* {{{ */
5493        if(!$group || !$requestUser)
5494            return -1;
5495
5496        $db = $this->_document->getDMS()->getDB();
5497
5498        if(!$group->isType('group'))
5499            return -1;
5500
5501        // Check if the group is on the review list at all.
5502        $reviewStatus = $group->getReviewStatus($this->_document->getID(), $this->_version);
5503        if (is_bool($reviewStatus) && !$reviewStatus) {
5504            return false;
5505        }
5506        if (count($reviewStatus)==0) {
5507            // User is not assigned to review this document. No action required.
5508            // Return an error.
5509            return -3;
5510        }
5511        if ((int) $reviewStatus[0]["status"]==-2) {
5512            // Group has been deleted from reviewers
5513            return -4;
5514        }
5515
5516        // Check if the status is really different from the current status
5517        if ($reviewStatus[0]["status"] == $status)
5518            return 0;
5519
5520        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`,
5521            `comment`, `date`, `userID`) ".
5522            "VALUES ('". $reviewStatus[0]["reviewID"] ."', '".
5523            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
5524            $requestUser->getID() ."')";
5525        $res=$db->getResult($queryStr);
5526        if (is_bool($res) && !$res)
5527            return false;
5528
5529        $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
5530        if($file) {
5531            SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'r' . $reviewLogID);
5532        }
5533        return $reviewLogID;
5534 } /* }}} */
5535
5536    /**
5537     * Add user as new approver
5538     *
5539     * @param object $user user in charge for the approval
5540     * @param object $requestUser user requesting the operation (usually the
5541     * currently logged in user)
5542     *
5543     * @return integer|false if > 0 the id of the approval log, if < 0 the error
5544     * code, false in case of an sql error
5545     */
5546    public function addIndApprover($user, $requestUser) { /* {{{ */
5547        if(!$user || !$requestUser)
5548            return -1;
5549
5550        $db = $this->_document->getDMS()->getDB();
5551
5552        if(!$user->isType('user'))
5553            return -1;
5554
5555        $userID = $user->getID();
5556
5557        // Get the list of users and groups with read access to this document.
5558        if($this->_document->getAccessMode($user) < M_READ) {
5559            return -2;
5560        }
5561
5562        // Check if the user has already been added to the approvers list.
5563        $approvalStatus = $user->getApprovalStatus($this->_document->getID(), $this->_version);
5564        if (is_bool($approvalStatus) && !$approvalStatus) {
5565            return false;
5566        }
5567        $indstatus = false;
5568        if (count($approvalStatus["indstatus"]) > 0) {
5569            $indstatus = array_pop($approvalStatus["indstatus"]);
5570            if($indstatus["status"]!=-2) {
5571                // User is already on the list of approverss; return an error.
5572                return -3;
5573            }
5574        }
5575
5576        if ( !$indstatus || (isset($indstatus["status"]) && $indstatus["status"]!=-2)) {
5577            // Add the user into the approvers database.
5578            $queryStr = "INSERT INTO `tblDocumentApprovers` (`documentID`, `version`, `type`, `required`) ".
5579                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '0', '". $userID ."')";
5580            $res = $db->getResult($queryStr);
5581            if (is_bool($res) && !$res) {
5582                return false;
5583            }
5584            $approveID = $db->getInsertID('tblDocumentApprovers', 'approveID');
5585        }
5586        else {
5587            $approveID = isset($indstatus["approveID"]) ? $indstatus["approveID"] : NULL;
5588        }
5589
5590        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
5591            "VALUES ('". $approveID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5592        $res = $db->getResult($queryStr);
5593        if (is_bool($res) && !$res) {
5594            return false;
5595        }
5596
5597        $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
5598        $db->dropTemporaryTable('ttapproveid');
5599        return $approveLogID;
5600    } /* }}} */
5601
5602    /**
5603     * Add group as new approver
5604     *
5605     * @param object $group group in charge for the approval
5606     * @param object $requestUser user requesting the operation (usually the
5607     * currently logged in user)
5608     *
5609     * @return integer|false if > 0 the id of the approval log, if < 0 the error
5610     * code, false in case of an sql error
5611     */
5612    public function addGrpApprover($group, $requestUser) { /* {{{ */
5613        if(!$group || !$requestUser)
5614            return -1;
5615
5616        $db = $this->_document->getDMS()->getDB();
5617
5618        if(!$group->isType('group'))
5619            return -1;
5620
5621        $groupID = $group->getID();
5622
5623        // Get the list of users and groups with read access to this document.
5624        if (!isset($this->_readAccessList)) {
5625            // TODO: error checking.
5626            $this->_readAccessList = $this->_document->getReadAccessList();
5627        }
5628        $approved = false;
5629        foreach ($this->_readAccessList["groups"] as $appGroup) {
5630            if ($groupID == $appGroup->getID()) {
5631                $approved = true;
5632                break;
5633            }
5634        }
5635        if (!$approved) {
5636            return -2;
5637        }
5638
5639        // Check if the group has already been added to the approver list.
5640        $approvalStatus = $group->getApprovalStatus($this->_document->getID(), $this->_version);
5641        if (is_bool($approvalStatus) && !$approvalStatus) {
5642            return false;
5643        }
5644        if (count($approvalStatus) > 0 && $approvalStatus[0]["status"]!=-2) {
5645            // Group is already on the list of approvers; return an error.
5646            return -3;
5647        }
5648
5649        // Add the group into the approver database.
5650        if (!isset($approvalStatus[0]["status"]) || (isset($approvalStatus[0]["status"]) && $approvalStatus[0]["status"]!=-2)) {
5651            $queryStr = "INSERT INTO `tblDocumentApprovers` (`documentID`, `version`, `type`, `required`) ".
5652                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '1', '". $groupID ."')";
5653            $res = $db->getResult($queryStr);
5654            if (is_bool($res) && !$res) {
5655                return false;
5656            }
5657            $approveID = $db->getInsertID('tblDocumentApprovers', 'approveID');
5658        }
5659        else {
5660            $approveID = isset($approvalStatus[0]["approveID"])?$approvalStatus[0]["approveID"]:NULL;
5661        }
5662
5663        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
5664            "VALUES ('". $approveID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5665        $res = $db->getResult($queryStr);
5666        if (is_bool($res) && !$res) {
5667            return false;
5668        }
5669
5670        $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
5671        $db->dropTemporaryTable('ttapproveid');
5672        return $approveLogID;
5673    } /* }}} */
5674
5675    /**
5676     * Sets approval status of a document content for a user
5677     *
5678     * This method can be used to approve or reject a document content, or
5679     * to reset its approval state. In most cases this function will be
5680     * called by an user, but  an admin may set the approval for
5681     * somebody else.
5682     * It is first checked if the user is in the list of approvers at all.
5683     * Then it is check if the approval status is already -2. In both cases
5684     * the function returns with an error.
5685     *
5686     * @see SeedDMS_Core_DocumentContent::setReviewByInd()
5687     *
5688     * @param object  $user user in charge for doing the approval
5689     * @param object  $requestUser user actually calling this function
5690     * @param integer $status the status of the approval, possible values are
5691     *        0=unprocessed (maybe used to reset a status)
5692     *        1=approved,
5693     *       -1=rejected,
5694     *       -2=user is deleted (use {link
5695     *       SeedDMS_Core_DocumentContent::delIndApprover} instead)
5696     * @param string $comment approval comment
5697     *
5698     * @return integer|bool new review log id, error code 0 till -4,
5699     * false in case of an sql error
5700     */
5701    public function setApprovalByInd($user, $requestUser, $status, $comment, $file='') { /* {{{ */
5702        if(!$user || !$requestUser)
5703            return -1;
5704
5705        $db = $this->_document->getDMS()->getDB();
5706
5707        if(!$user->isType('user'))
5708            return -1;
5709
5710        // Check if the user is on the approval list at all.
5711        $approvalStatus = $user->getApprovalStatus($this->_document->getID(), $this->_version);
5712        if (is_bool($approvalStatus) && !$approvalStatus) {
5713            return false;
5714        }
5715        if (count($approvalStatus["indstatus"])==0) {
5716            // User is not assigned to approve this document. No action required.
5717            // Return an error.
5718            return -3;
5719        }
5720        $indstatus = array_pop($approvalStatus["indstatus"]);
5721        if ($indstatus["status"]==-2) {
5722            // User has been deleted from approvers
5723            return -4;
5724        }
5725        // Check if the status is really different from the current status
5726        if ($indstatus["status"] == $status)
5727            return 0;
5728
5729        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`,
5730            `comment`, `date`, `userID`) ".
5731            "VALUES ('". $indstatus["approveID"] ."', '".
5732            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
5733            $requestUser->getID() ."')";
5734        $res=$db->getResult($queryStr);
5735        if (is_bool($res) && !$res)
5736            return false;
5737
5738        $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
5739        if($file) {
5740            SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'a' . $approveLogID);
5741        }
5742        return $approveLogID;
5743    } /* }}} */
5744
5745    /**
5746     * Add another entry to approval log which resets the status
5747     *
5748     * This method will not delete anything from the database, but will add
5749     * a new approval log entry which sets the status to 0. This is only allowed
5750     * if the current status is either 1 (approved) or -1 (rejected).
5751     *
5752     * After calling this method SeedDMS_Core_DocumentContent::verifyStatus()
5753     * should be called to recalculate the document status.
5754     *
5755     * @param integer $approveid id of approval
5756     * @param SeedDMS_Core_User $requestUser user requesting the removal
5757     * @param string $comment comment
5758     *
5759     * @return integer|bool true if successful, error code < 0,
5760     * false in case of an sql error
5761     */
5762    public function removeApproval($approveid, $requestUser, $comment='') { /* {{{ */
5763        $db = $this->_document->getDMS()->getDB();
5764
5765        // Check to see if the user can be removed from the approval list.
5766        $approvals = $this->getApprovalStatus();
5767        if (is_bool($approvals) && !$approvals) {
5768            return false;
5769        }
5770        $approvalStatus = null;
5771        foreach($approvals as $approval) {
5772            if($approval['approveID'] == $approveid) {
5773                $approvalStatus = $approval;
5774                break;
5775            }
5776        }
5777        if(!$approvalStatus)
5778            return -2;
5779
5780        // The approval log entry may only be removed if the status is 1 or -1
5781        if ($approvalStatus["status"] != 1 && $approvalStatus["status"] != -1)
5782            return -3;
5783
5784        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`,
5785            `comment`, `date`, `userID`) ".
5786            "VALUES ('". $approvalStatus["approveID"] ."', '0', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
5787            $requestUser->getID() ."')";
5788        $res=$db->getResult($queryStr);
5789        if (is_bool($res) && !$res)
5790            return false;
5791
5792        return true;
5793 } /* }}} */
5794
5795    /**
5796     * Sets approval status of a document content for a group
5797     *
5798     * The functions behaves like
5799     * {link SeedDMS_Core_DocumentContent::setApprovalByInd} but does it for
5800     * a group instead of a user
5801     */
5802    public function setApprovalByGrp($group, $requestUser, $status, $comment, $file='') { /* {{{ */
5803        if(!$group || !$requestUser)
5804            return -1;
5805
5806        $db = $this->_document->getDMS()->getDB();
5807
5808        if(!$group->isType('group'))
5809            return -1;
5810
5811        // Check if the group is on the approval list at all.
5812        $approvalStatus = $group->getApprovalStatus($this->_document->getID(), $this->_version);
5813        if (is_bool($approvalStatus) && !$approvalStatus) {
5814            return false;
5815        }
5816        if (count($approvalStatus)==0) {
5817            // User is not assigned to approve this document. No action required.
5818            // Return an error.
5819            return -3;
5820        }
5821        if ($approvalStatus[0]["status"]==-2) {
5822            // Group has been deleted from approvers
5823            return -4;
5824        }
5825
5826        // Check if the status is really different from the current status
5827        if ($approvalStatus[0]["status"] == $status)
5828            return 0;
5829
5830        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`,
5831            `comment`, `date`, `userID`) ".
5832            "VALUES ('". $approvalStatus[0]["approveID"] ."', '".
5833            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
5834            $requestUser->getID() ."')";
5835        $res=$db->getResult($queryStr);
5836        if (is_bool($res) && !$res)
5837            return false;
5838
5839        $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
5840        if($file) {
5841            SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'a' . $approveLogID);
5842        }
5843        return $approveLogID;
5844 } /* }}} */
5845
5846    function addIndRecipient($user, $requestUser) { /* {{{ */
5847        $db = $this->_document->getDMS()->getDB();
5848
5849        if(!$user || !$requestUser)
5850            return -1;
5851
5852        if(!$user->isType('user'))
5853            return -1;
5854
5855        $userID = $user->getID();
5856
5857        // Get the list of users and groups with read access to this document.
5858        if($this->_document->getAccessMode($user) < M_READ) {
5859            return -2;
5860        }
5861
5862        // Check to see if the user has already been added to the receipt list.
5863        $receiptStatus = $user->getReceiptStatus($this->_document->getID(), $this->_version);
5864        if (is_bool($receiptStatus) && !$receiptStatus) {
5865            return -1;
5866        }
5867        $indstatus = false;
5868        if (count($receiptStatus["indstatus"]) > 0) {
5869            $indstatus = array_pop($receiptStatus["indstatus"]);
5870            if($indstatus["status"]!=-2) {
5871                // User is already on the list of recipients; return an error.
5872                return -3;
5873            }
5874        }
5875
5876        // Add the user into the recipients database.
5877        if (!$indstatus || ($indstatus && $indstatus["status"]!=-2)) {
5878            $queryStr = "INSERT INTO `tblDocumentRecipients` (`documentID`, `version`, `type`, `required`) ".
5879                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '0', '". $userID ."')";
5880            $res = $db->getResult($queryStr);
5881            if (is_bool($res) && !$res) {
5882                return -1;
5883            }
5884            $receiptID = $db->getInsertID('tblDocumentRecipients', 'receiptID');
5885        }
5886        else {
5887            $receiptID = isset($indstatus["receiptID"]) ? $indstatus["receiptID"] : NULL;
5888        }
5889
5890        $queryStr = "INSERT INTO `tblDocumentReceiptLog` (`receiptID`, `status`, `comment`, `date`, `userID`) ".
5891            "VALUES ('". $receiptID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5892        $res = $db->getResult($queryStr);
5893        if (is_bool($res) && !$res) {
5894            return -1;
5895        }
5896
5897        // Add recipient to event notification table.
5898        //$this->_document->addNotify($userID, true);
5899
5900        $receiptLogID = $db->getInsertID('tblDocumentReceiptLog', 'receiptLogID');
5901        $db->dropTemporaryTable('ttreceiptid');
5902        return $receiptLogID;
5903    } /* }}} */
5904
5905    function addGrpRecipient($group, $requestUser) { /* {{{ */
5906        $db = $this->_document->getDMS()->getDB();
5907
5908        if(!$group || !$requestUser)
5909            return -1;
5910
5911        if(!$group->isType('group'))
5912            return -1;
5913
5914        $groupID = $group->getID();
5915
5916        // Get the list of users and groups with read access to this document.
5917        if (!isset($this->_readAccessList)) {
5918            // TODO: error checking.
5919            $this->_readAccessList = $this->_document->getReadAccessList();
5920        }
5921        $approved = false;
5922        foreach ($this->_readAccessList["groups"] as $appGroup) {
5923            if ($groupID == $appGroup->getID()) {
5924                $approved = true;
5925                break;
5926            }
5927        }
5928        if (!$approved) {
5929            return -2;
5930        }
5931
5932        // Check to see if the group has already been added to the review list.
5933        $receiptStatus = $group->getReceiptStatus($this->_document->getID(), $this->_version);
5934        if (is_bool($receiptStatus) && !$receiptStatus) {
5935            return -1;
5936        }
5937        $status = false;
5938        if (count($receiptStatus) > 0) {
5939            $status = array_pop($receiptStatus);
5940            if($status["status"]!=-2) {
5941                // User is already on the list of recipients; return an error.
5942                return -3;
5943            }
5944        }
5945
5946        // Add the group into the recipients database.
5947        if (!$status || ($status && $status["status"]!=-2)) {
5948            $queryStr = "INSERT INTO `tblDocumentRecipients` (`documentID`, `version`, `type`, `required`) ".
5949                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '1', '". $groupID ."')";
5950            $res = $db->getResult($queryStr);
5951            if (is_bool($res) && !$res) {
5952                return -1;
5953            }
5954            $receiptID = $db->getInsertID('tblDocumentRecipients', 'receiptID');
5955        }
5956        else {
5957            $receiptID = isset($status["receiptID"]) ? $status["receiptID"] : NULL;
5958        }
5959
5960        $queryStr = "INSERT INTO `tblDocumentReceiptLog` (`receiptID`, `status`, `comment`, `date`, `userID`) ".
5961            "VALUES ('". $receiptID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5962        $res = $db->getResult($queryStr);
5963        if (is_bool($res) && !$res) {
5964            return -1;
5965        }
5966
5967        $receiptLogID = $db->getInsertID('tblDocumentReceiptLog', 'receiptLogID');
5968        $db->dropTemporaryTable('ttreceiptid');
5969        return $receiptLogID;
5970    } /* }}} */
5971
5972    /**
5973     * Add an individual revisor to the document content
5974     *
5975     * This function adds a user as a revisor but doesn't start the
5976     * revision workflow by default. This behaviour is different from all
5977     * other workflows (approval, review, receipt), because it adds
5978     * an initial entry in the revision log, which marks the revision as
5979     * 'sleeping'. The workflow is started at a later point in time by adding 
5980     * the second entry in the revision log which puts it into 'waiting'.
5981     *
5982     * @param object $user user to be added as a revisor
5983     * @param object $requestUser user requesting the addition
5984     * @return integer 0 if successful otherwise a value < 0
5985     */
5986    function addRevisor($object, $requestUser) { /* {{{ */
5987        $dms = $this->_document->getDMS();
5988        $db = $dms->getDB();
5989
5990        if(!$object || !$requestUser)
5991            return -1;
5992
5993        // Check to see if the user has already been added to the revisor list.
5994        $revisionStatus = $object->getRevisionStatus($this->_document->getID(), $this->_version);
5995        if (is_bool($revisionStatus) && !$revisionStatus) {
5996            return -1;
5997        }
5998
5999        /* getRevisionStatus() returns an array with either an element
6000         * 'indstatus' (user) or no element (group) containing the revision log
6001         */
6002        if(get_class($object) == $dms->getClassname('user')) {
6003            $revisionStatus = $revisionStatus['indstatus'];
6004            $type = 0;
6005
6006            // Get the list of users and groups with read access to this document.
6007            if($this->_document->getAccessMode($object) < M_READ) {
6008                return -2;
6009            }
6010        } elseif(get_class($object) == $dms->getClassname('group')) {
6011            $type = 1;
6012
6013            // Get the list of users and groups with read access to this document.
6014            if($this->_document->getGroupAccessMode($object) < M_READ) {
6015                return -2;
6016            }
6017        } else {
6018            return -1;
6019        }
6020
6021        /* There are two cases: 1. the user has not been added at all or 2.
6022         * the user was added before but has been removed later. In both
6023         * cases the user may be added. In case 2. 'indstatus' will be set
6024         * and the last status is -2. If it is not -2, then the user is still
6025         * in the process and cannot be added again.
6026         */
6027        $indstatus = false;
6028        if($revisionStatus) {
6029            if (count($revisionStatus) > 0) {
6030                $indstatus = array_pop($revisionStatus);
6031                if($indstatus["status"] != S_LOG_USER_REMOVED) {
6032                    // User is already on the list of recipients; return an error.
6033                    return -3;
6034                }
6035            }
6036        }
6037
6038        // Add the user into the revisors database.
6039        if (!$indstatus) {
6040            $queryStr = "INSERT INTO `tblDocumentRevisors` (`documentID`, `version`, `type`, `required`) ".
6041                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '". $type ."', '". $object->getID() ."')";
6042            $res = $db->getResult($queryStr);
6043            if (is_bool($res) && !$res) {
6044                return -1;
6045            }
6046            $revisionID = $db->getInsertID('tblDocumentRevisors', 'revisionID');
6047        } else {
6048            $revisionID = isset($indstatus["revisionID"]) ? $indstatus["revisionID"] : NULL;
6049        }
6050
6051        /* If a user is added when the revision has already been startet, then
6052         * put it into S_LOG_WAITING otherwise into S_LOG_SLEEPING. Attention, if a
6053         * document content is in any other status but S_IN_REVISION, then it will
6054         * end up in S_LOG_SLEEPING. As this method is also called by removeFromProcesses()
6055         * when another user takes over the processes, it may happen that revisions
6056         * of document contents in status e.g. S_OBSOLETE, S_EXPIRED will change its
6057         * status from S_LOG_WAITING to S_LOG_SLEEPING. 
6058         * This could only be fixed if this method could set an initial revision status
6059         * by possibly passing it as another parameter to the method.
6060         */
6061        $st=$this->getStatus();
6062        $queryStr = "INSERT INTO `tblDocumentRevisionLog` (`revisionID`, `status`, `comment`, `date`, `userID`) ".
6063            "VALUES ('". $revisionID ."', '".($st["status"] == S_IN_REVISION ? S_LOG_WAITING : S_LOG_SLEEPING)."', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
6064        $res = $db->getResult($queryStr);
6065        if (is_bool($res) && !$res) {
6066            return -1;
6067        }
6068
6069        $revisionLogID = $db->getInsertID('tblDocumentRevisionLog', 'revisionLogID');
6070        $db->dropTemporaryTable('ttrevisionid');
6071        return $revisionLogID;
6072    } /* }}} */
6073
6074    function addIndRevisor($user, $requestUser, $donotstart=true) { /* {{{ */
6075        if($user && !$user->isType('user'))
6076            return -1;
6077
6078        return self::addRevisor($user, $requestUser, $donotstart);
6079    } /* }}} */
6080
6081    function addGrpRevisor($group, $requestUser, $donotstart=true) { /* {{{ */
6082        if($group && !$group->isType('group'))
6083            return -1;
6084
6085        return self::addRevisor($group, $requestUser, $donotstart);
6086    } /* }}} */
6087
6088    /**
6089     * Add a receipt to the document content
6090     *
6091     * This method will add an entry to the table tblDocumentReceiptLog.
6092     * It will first check if the user is ment to receipt the document version.
6093     * If not the return value is -3.
6094     * Next it will check if the user has been removed from the list of
6095     * recipients. In that case -4 will be returned.
6096     * If the given receipt has been set by the user before, it cannot
6097     * be set again and 0 will be returned. Ð†f the receipt could be succesfully
6098     * added, the receiptview log id will be returned.
6099     *
6100     * @see SeedDMS_Core_DocumentContent::setApprovalByInd()
6101     * @param object $user user doing the receipt
6102     * @param object $requestUser user asking for the receipt, this is mostly
6103     * @param integer $status the status of the receipt, possible values are
6104     *        0=unprocessed (may be used to reset a status)
6105     *        1=received,
6106     *       -1=rejected,
6107     *       -2=user is deleted (use {link
6108     *       SeedDMS_Core_DocumentContent::delIndRecipient} instead)
6109     * the user currently logged in.
6110     * @return integer new receipt log id
6111     */
6112    function setReceiptByInd($user, $requestUser, $status, $comment) { /* {{{ */
6113        $db = $this->_document->getDMS()->getDB();
6114
6115        if(!$user || !$requestUser)
6116            return -1;
6117
6118        if(!$user->isType('user'))
6119            return -1;
6120
6121        // Check to see if the user can be removed from the review list.
6122        $receiptStatus = $user->getReceiptStatus($this->_document->getID(), $this->_version);
6123        if (is_bool($receiptStatus) && !$receiptStatus) {
6124            return -1;
6125        }
6126        if (count($receiptStatus["indstatus"])==0) {
6127            // User is not assigned to receipt this document. No action required.
6128            // Return an error.
6129            return -3;
6130        }
6131        $indstatus = array_pop($receiptStatus["indstatus"]);
6132        if ($indstatus["status"] == S_LOG_USER_REMOVED) {
6133            // User has been deleted from recipients
6134            return -4;
6135        }
6136        // Check if the status is really different from the current status
6137        if ($indstatus["status"] == $status)
6138            return 0;
6139
6140        $queryStr = "INSERT INTO `tblDocumentReceiptLog` (`receiptID`, `status`,
6141            `comment`, `date`, `userID`) ".
6142            "VALUES ('". $indstatus["receiptID"] ."', '".
6143            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
6144            $requestUser->getID() ."')";
6145        $res=$db->getResult($queryStr);
6146        if (is_bool($res) && !$res)
6147            return -1;
6148        else {
6149            $receiptLogID = $db->getInsertID('tblDocumentReceiptLog', 'receiptLogID');
6150            return $receiptLogID;
6151        }
6152 } /* }}} */
6153
6154    /**
6155     * Add a receipt to the document content
6156     *
6157     * This method is similar to
6158     * {@see SeedDMS_Core_DocumentContent::setReceiptByInd()} but adds a receipt
6159     * for a group instead of a user.
6160     *
6161     * @param object $group group doing the receipt
6162     * @param object $requestUser user asking for the receipt, this is mostly
6163     * the user currently logged in.
6164     * @return integer new receipt log id
6165     */
6166    function setReceiptByGrp($group, $requestUser, $status, $comment) { /* {{{ */
6167        $db = $this->_document->getDMS()->getDB();
6168
6169        if(!$group || !$requestUser)
6170            return -1;
6171
6172        if(!$group->isType('group'))
6173            return -1;
6174
6175        // Check to see if the user can be removed from the recipient list.
6176        $receiptStatus = $group->getReceiptStatus($this->_document->getID(), $this->_version);
6177        if (is_bool($receiptStatus) && !$receiptStatus) {
6178            return -1;
6179        }
6180        if (count($receiptStatus)==0) {
6181            // User is not assigned to receipt this document. No action required.
6182            // Return an error.
6183            return -3;
6184        }
6185        $grpstatus = array_pop($receiptStatus);
6186        if ($grpstatus["status"] == S_LOG_USER_REMOVED) {
6187            // Group has been deleted from recipients
6188            return -4;
6189        }
6190
6191        // Check if the status is really different from the current status
6192        if ($grpstatus["status"] == $status)
6193            return 0;
6194
6195        $queryStr = "INSERT INTO `tblDocumentReceiptLog` (`receiptID`, `status`,
6196            `comment`, `date`, `userID`) ".
6197            "VALUES ('". $grpstatus["receiptID"] ."', '".
6198            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
6199            $requestUser->getID() ."')";
6200        $res=$db->getResult($queryStr);
6201        if (is_bool($res) && !$res)
6202            return -1;
6203        else {
6204            $receiptLogID = $db->getInsertID('tblDocumentReceiptLog', 'receiptLogID');
6205            return $receiptLogID;
6206        }
6207 } /* }}} */
6208
6209    /**
6210     * Add a revision to the document content
6211     *
6212     * This method will add an entry to the table tblDocumentRevisionLog.
6213     * It will first check if the user is ment to revision the document version.
6214     * If not the return value is -3.
6215     * Next it will check if the user has been removed from the list of
6216     * recipients. In that case -4 will be returned.
6217     * If the given revision has been set by the user before, it cannot
6218     * be set again and 0 will be returned. Ð†f the revision could be succesfully
6219     * added, the revision log id will be returned.
6220     *
6221     * @see SeedDMS_Core_DocumentContent::setApprovalByInd()
6222     * @param object $user user doing the revision
6223     * @param object $requestUser user asking for the revision, this is mostly
6224     * the user currently logged in.
6225     * @param integer $status the status of the revision, possible values are
6226     *        0=unprocessed (may be used to reset a status)
6227     *        1=revised,
6228     *       -2=user is deleted (use {link
6229     *       SeedDMS_Core_DocumentContent::delIndRecipient} instead)
6230     *       -3=workflow revision is sleeping
6231     * @return integer new revision log id, 0, or a value < 0. 0 means the
6232     * status has not changed because the new status is equal the current
6233     * status. A value < 0 indicate
6234     * an error. -1: internal error, -3: user may not revise this document
6235     * -4: the user has been removed from the list of revisors,
6236     * -5: the revision has not been started at all.
6237     */
6238    function setRevision($object, $requestUser, $status, $comment) { /* {{{ */
6239        $dms = $this->_document->getDMS();
6240        $db = $dms->getDB();
6241
6242        if(!$object || !$requestUser)
6243            return -1;
6244
6245        // Check to see if the user/group can be removed from the review list.
6246        $revisionStatus = $object->getRevisionStatus($this->_document->getID(), $this->_version);
6247        if (is_bool($revisionStatus) && !$revisionStatus) {
6248            return -1;
6249        }
6250
6251        /* getRevisionStatus() returns an array with either an element
6252         * 'indstatus' (user) or 'status' (group) containing the revision log
6253         */
6254        if(get_class($object) == $dms->getClassname('user')) {
6255            $revisionStatus = $revisionStatus['indstatus'];
6256        } elseif(get_class($object) == $dms->getClassname('group')) {
6257        } else {
6258            return -1;
6259        }
6260
6261        if (!$revisionStatus) {
6262            // User is not assigned to revision this document. No action required.
6263            // Return an error.
6264            return -3;
6265        }
6266        $indstatus = array_pop($revisionStatus);
6267
6268        /* check if revision workflow has been started already */
6269        if($indstatus['status'] == S_LOG_SLEEPING && ($status == S_LOG_REJECTED || $status == S_LOG_ACCEPTED))
6270            return -5;
6271
6272        if ($indstatus["status"] == -2) {
6273            // User has been deleted from recipients
6274            return -4;
6275        }
6276        // Check if the status is really different from the current status
6277        if ($indstatus["status"] == $status)
6278            return 0;
6279
6280        $queryStr = "INSERT INTO `tblDocumentRevisionLog` (`revisionID`, `status`,
6281            `comment`, `date`, `userID`) ".
6282            "VALUES ('". $indstatus["revisionID"] ."', '".
6283            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
6284            $requestUser->getID() ."')";
6285        $res=$db->getResult($queryStr);
6286        if (is_bool($res) && !$res)
6287            return -1;
6288        else {
6289            $revisionLogID = $db->getInsertID('tblDocumentRevisionLog', 'revisionLogID');
6290            return $revisionLogID;
6291        }
6292 } /* }}} */
6293
6294    function setRevisionByInd($user, $requestUser, $status, $comment) { /* {{{ */
6295        if(!$user || !$user->isType('user'))
6296            return -1;
6297
6298        return self::setRevision($user, $requestUser, $status, $comment);
6299    } /* }}} */
6300
6301    function setRevisionByGrp($group, $requestUser, $status, $comment) { /* {{{ */
6302        if(!$group || !$group->isType('group'))
6303            return -1;
6304
6305        return self::setRevision($group, $requestUser, $status, $comment);
6306    } /* }}} */
6307
6308    public function delIndReviewer($user, $requestUser, $msg='') { /* {{{ */
6309        $db = $this->_document->getDMS()->getDB();
6310
6311        if(!$user || !$user->isType('user'))
6312            return -1;
6313
6314        // Check to see if the user can be removed from the review list.
6315        $reviewStatus = $user->getReviewStatus($this->_document->getID(), $this->_version);
6316        if (is_bool($reviewStatus) && !$reviewStatus) {
6317            return false;
6318        }
6319        if (count($reviewStatus["indstatus"])==0) {
6320            // User is not assigned to review this document. No action required.
6321            // Return an error.
6322            return -2;
6323        }
6324        $indstatus = array_pop($reviewStatus["indstatus"]);
6325        if ($indstatus["status"]!=0) {
6326            // User has already submitted a review or has already been deleted;
6327            // return an error.
6328            return -3;
6329        }
6330
6331        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
6332            "VALUES ('". $indstatus["reviewID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
6333        $res = $db->getResult($queryStr);
6334        if (is_bool($res) && !$res) {
6335            return false;
6336        }
6337
6338        return 0;
6339    } /* }}} */
6340
6341    public function delGrpReviewer($group, $requestUser, $msg='') { /* {{{ */
6342        $db = $this->_document->getDMS()->getDB();
6343
6344        if(!$group->isType('group'))
6345            return -1;
6346
6347        $groupID = $group->getID();
6348
6349        // Check to see if the user can be removed from the review list.
6350        $reviewStatus = $group->getReviewStatus($this->_document->getID(), $this->_version);
6351        if (is_bool($reviewStatus) && !$reviewStatus) {
6352            return false;
6353        }
6354        if (count($reviewStatus)==0) {
6355            // User is not assigned to review this document. No action required.
6356            // Return an error.
6357            return -2;
6358        }
6359        if ($reviewStatus[0]["status"]!=0) {
6360            // User has already submitted a review or has already been deleted;
6361            // return an error.
6362            return -3;
6363        }
6364
6365        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
6366            "VALUES ('". $reviewStatus[0]["reviewID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
6367        $res = $db->getResult($queryStr);
6368        if (is_bool($res) && !$res) {
6369            return false;
6370        }
6371
6372        return 0;
6373    } /* }}} */
6374
6375    public function delIndApprover($user, $requestUser, $msg='') { /* {{{ */
6376        $db = $this->_document->getDMS()->getDB();
6377
6378        if(!$user->isType('user'))
6379            return -1;
6380
6381        $userID = $user->getID();
6382
6383        // Check if the user is on the approval list at all.
6384        $approvalStatus = $user->getApprovalStatus($this->_document->getID(), $this->_version);
6385        if (is_bool($approvalStatus) && !$approvalStatus) {
6386            return false;
6387        }
6388        if (count($approvalStatus["indstatus"])==0) {
6389            // User is not assigned to approve this document. No action required.
6390            // Return an error.
6391            return -2;
6392        }
6393        $indstatus = array_pop($approvalStatus["indstatus"]);
6394        if ($indstatus["status"]!=0) {
6395            // User has already submitted an approval or has already been deleted;
6396            // return an error.
6397            return -3;
6398        }
6399
6400        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
6401            "VALUES ('". $indstatus["approveID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
6402        $res = $db->getResult($queryStr);
6403        if (is_bool($res) && !$res) {
6404            return false;
6405        }
6406
6407        return 0;
6408    } /* }}} */
6409
6410    public function delGrpApprover($group, $requestUser, $msg='') { /* {{{ */
6411        $db = $this->_document->getDMS()->getDB();
6412
6413        if(!$group->isType('group'))
6414            return -1;
6415
6416        $groupID = $group->getID();
6417
6418        // Check if the group is on the approval list at all.
6419        $approvalStatus = $group->getApprovalStatus($this->_document->getID(), $this->_version);
6420        if (is_bool($approvalStatus) && !$approvalStatus) {
6421            return false;
6422        }
6423        if (count($approvalStatus)==0) {
6424            // User is not assigned to approve this document. No action required.
6425            // Return an error.
6426            return -2;
6427        }
6428        if ($approvalStatus[0]["status"]!=0) {
6429            // User has already submitted an approval or has already been deleted;
6430            // return an error.
6431            return -3;
6432        }
6433
6434        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
6435            "VALUES ('". $approvalStatus[0]["approveID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
6436        $res = $db->getResult($queryStr);
6437        if (is_bool($res) && !$res) {
6438            return false;
6439        }
6440
6441        return 0;
6442    } /* }}} */
6443
6444    function delIndRecipient($user, $requestUser, $msg='') { /* {{{ */
6445        $db = $this->_document->getDMS()->getDB();
6446
6447        $userID = $user->getID();
6448
6449        // Check to see if the user can be removed from the recipient list.
6450        $receiptStatus = $user->getReceiptStatus($this->_document->getID(), $this->_version);
6451        if (is_bool($receiptStatus) && !$receiptStatus) {
6452            return -1;
6453        }
6454        if (count($receiptStatus["indstatus"])==0) {
6455            // User is not assigned to receipt this document. No action required.
6456            // Return an error.
6457            return -2;
6458        }
6459        $indstatus = array_pop($receiptStatus["indstatus"]);
6460        if ($indstatus["status"]!=0) {
6461            // User has already submitted a receipt or has already been deleted;
6462            // return an error.
6463            return -3;
6464        }
6465
6466        $queryStr = "INSERT INTO `tblDocumentReceiptLog` (`receiptID`, `status`, `comment`, `date`, `userID`) ".
6467            "VALUES ('". $indstatus["receiptID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
6468        $res = $db->getResult($queryStr);
6469        if (is_bool($res) && !$res) {
6470            return -1;
6471        }
6472
6473        return 0;
6474    } /* }}} */
6475
6476    function delGrpRecipient($group, $requestUser, $msg='') { /* {{{ */
6477        $db = $this->_document->getDMS()->getDB();
6478
6479        $groupID = $group->getID();
6480
6481        // Check to see if the user can be removed from the recipient list.
6482        $receiptStatus = $group->getReceiptStatus($this->_document->getID(), $this->_version);
6483        if (is_bool($receiptStatus) && !$receiptStatus) {
6484            return -1;
6485        }
6486        if (count($receiptStatus)==0) {
6487            // User is not assigned to receipt this document. No action required.
6488            // Return an error.
6489            return -2;
6490        }
6491        $status = array_pop($receiptStatus);
6492        if ($status["status"]!=0) {
6493            // User has already submitted a receipt or has already been deleted;
6494            // return an error.
6495            return -3;
6496        }
6497
6498        $queryStr = "INSERT INTO `tblDocumentReceiptLog` (`receiptID`, `status`, `comment`, `date`, `userID`) ".
6499            "VALUES ('". $status["receiptID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
6500        $res = $db->getResult($queryStr);
6501        if (is_bool($res) && !$res) {
6502            return -1;
6503        }
6504
6505        return 0;
6506    } /* }}} */
6507
6508    /**
6509     * Removes a user from the revision workflow
6510     *
6511     * This methods behaves differently from one in the other workflows, e.g.
6512     * {@see SeedDMS_Core_DocumentContent::delIndReviewer}, because it
6513     * also takes into account if the workflow has been started already.
6514     * A workflow has been started, when there are entries in the revision log.
6515     * If the revision workflow has not been started, then the user will
6516     * be silently removed from the list of revisors. If the workflow has
6517     * started already, then log entry will indicated the removal of the
6518     * user (just as it is done with the other workflows)
6519     *
6520     * @param object $object user/group which is to be removed
6521     * @param object $requestUser user requesting the removal
6522     * @return integer 0 if removal was successfull, -1 if an internal error
6523     * occured, -3 if the user is not in the list of revisors
6524     *
6525     */
6526    function delRevisor($object, $requestUser, $msg='') { /* {{{ */
6527        $dms = $this->_document->getDMS();
6528        $db = $dms->getDB();
6529
6530        // Check to see if the user/group can be removed from the revisor list.
6531        $revisionStatus = $object->getRevisionStatus($this->_document->getID(), $this->_version);
6532        if (is_bool($revisionStatus) && !$revisionStatus) {
6533            return -1;
6534        }
6535
6536        /* getRevisionStatus() returns an array with either an element
6537         * 'indstatus' (user) or no element (group) containing the revision log
6538         */
6539        if(get_class($object) == $dms->getClassname('user')) {
6540            $revisionStatus = $revisionStatus['indstatus'];
6541            $type = 0;
6542        } elseif(get_class($object) == $dms->getClassname('group')) {
6543            $type = 1;
6544        } else {
6545            return -1;
6546        }
6547
6548        if (!$revisionStatus) {
6549            // User is not assigned to revision this document. No action required.
6550            // Return an error.
6551            return -2;
6552        }
6553
6554        /* If the revision log doesn't contain an entry yet, then remove the
6555         * user/group from the list of revisors. The first case should not happen.
6556         */
6557        if(count($revisionStatus) == 0) {
6558            $queryStr = "DELETE from `tblDocumentRevisors` WHERE `documentID` = ". $this->_document->getID() ." AND `version` = ".$this->_version." AND `type` = ". $type ." AND `required` = ".$object->getID();
6559            if (!$db->getResult($queryStr)) {
6560                return -1;
6561            }
6562        } else {
6563            $indstatus = array_pop($revisionStatus);
6564            if ($indstatus["status"] != S_LOG_WAITING && $indstatus["status"] != S_LOG_SLEEPING) {
6565                // User has already submitted a revision or has already been deleted;
6566                // return an error.
6567                if($indstatus["status"] == S_LOG_USER_REMOVED)
6568                    return -3;
6569                else
6570                    return -4;
6571            }
6572
6573            $queryStr = "INSERT INTO `tblDocumentRevisionLog` (`revisionID`, `status`, `comment`, `date`, `userID`) ".
6574                "VALUES ('". $indstatus["revisionID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
6575            $res = $db->getResult($queryStr);
6576            if (is_bool($res) && !$res) {
6577                return -1;
6578            }
6579        }
6580
6581        return 0;
6582    } /* }}} */
6583
6584    function delIndRevisor($user, $requestUser, $msg='') { /* {{{ */
6585        return self::delRevisor($user, $requestUser, $msg);
6586    } /* }}} */
6587
6588    function delGrpRevisor($group, $requestUser, $msg='') { /* {{{ */
6589        return self::delRevisor($group, $requestUser, $msg);
6590    } /* }}} */
6591
6592    /**
6593     * Start a new revision workflow
6594     *
6595     * This function starts a new revision unless there are users/groups
6596     * having finished the previous revision. This means the log status
6597     * must be S_LOG_SLEEPING or the user/group was removed (S_LOG_USER_REMOVED)
6598     *
6599     * @param object $requestUser user requesting the revision start
6600     * @param string $msg message saved for the initial log message
6601     */
6602    function startRevision($requestUser, $msg='') { /* {{{ */
6603        $dms = $this->_document->getDMS();
6604        $db = $dms->getDB();
6605
6606        $revisionStatus = self::getRevisionStatus();
6607        if(!$revisionStatus)
6608            return false;
6609
6610        /* A new revision may only be started if we are not in the middle of
6611         * revision or the user/group has been removed from the workflow
6612         */
6613        /* Taken out, because it happened that a revision wasn't started for each revisor
6614         * but just for some.
6615         * Checking for each revisor not being sleeping prevented a second start of the
6616         * revision for the remaining revisors still sleeping.
6617        foreach($revisionStatus as $status) {
6618            if($status['status'] != S_LOG_SLEEPING && $status['status'] != S_LOG_USER_REMOVED)
6619                return false;
6620        }
6621         */
6622
6623        /* Make sure all Logs will be set to the right status, in order to
6624         * prevent inconsistent states. Actually it could be a feature to
6625         * force only some users/groups to revise the document, but for now
6626         * this may not be possible.
6627         */
6628        $db->startTransaction();
6629        $startedrev = false;
6630        foreach($revisionStatus as $status) {
6631            if($status['status'] == S_LOG_SLEEPING) {
6632                $queryStr = "INSERT INTO `tblDocumentRevisionLog` (`revisionID`, `status`,
6633                    `comment`, `date`, `userID`) ".
6634                    "VALUES ('". $status["revisionID"] ."', ".
6635                    S_LOG_WAITING.", ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '".
6636                    $requestUser->getID() ."')";
6637                $res=$db->getResult($queryStr);
6638                if (is_bool($res) && !$res) {
6639                    $db->rollbackTransaction();
6640                    return false;
6641                }
6642                $startedrev = true;
6643            }
6644        }
6645        /* Set status only if at least one revision was started */
6646        if($startedrev)
6647            if(!$this->setStatus(S_IN_REVISION, "Started revision scheduled for ".$this->getRevisionDate(), $requestUser)) {
6648                $db->rollbackTransaction();
6649                return false;
6650            }
6651        $db->commitTransaction();
6652        return true;
6653
6654    } /* }}} */
6655
6656    /**
6657     * Finish a revision workflow
6658     *
6659     * This function ends a revision This means the log status
6660     * is set back S_LOG_SLEEPING and the document status is set as
6661     * passed to the method. The function doesn't not check if all
6662     * users/groups has made it vote already.
6663     *
6664     * @param object $requestUser user requesting the revision start
6665     * @param integer $docstatus document status
6666     * @param string $msg message saved in revision log
6667     * @param string $msg message saved in document status log
6668     */
6669    function finishRevision($requestUser, $docstatus, $msg='', $docmsg='') { /* {{{ */
6670        $dms = $this->_document->getDMS();
6671        $db = $dms->getDB();
6672
6673        $revisionStatus = self::getRevisionStatus();
6674        if(!$revisionStatus)
6675            return false;
6676
6677        /* A revision may only be finished if it wasn't finished already
6678         */
6679        foreach($revisionStatus as $status) {
6680            if($status['status'] == S_LOG_SLEEPING)
6681                return false;
6682        }
6683
6684        /* Make sure all Logs will be set to the right status, in order to
6685         * prevent inconsistent states. Actually it could be a feature to
6686         * end only some users/groups to revise the document, but for now
6687         * this may not be possible.
6688         */
6689        $db->startTransaction();
6690        /* Does it make sense to put all revisions into sleeping mode? I guess
6691         * not. If a document was released or rejected the revision are useless
6692         * anyway 
6693         */
6694        foreach($revisionStatus as $status) {
6695            if($status['status'] != S_LOG_SLEEPING && $status['status'] != S_LOG_USER_REMOVED) {
6696                $queryStr = "INSERT INTO `tblDocumentRevisionLog` (`revisionID`, `status`,
6697                    `comment`, `date`, `userID`) ".
6698                    "VALUES ('". $status["revisionID"] ."', ".
6699                    S_LOG_SLEEPING.", ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '".
6700                    $requestUser->getID() ."')";
6701                $res=$db->getResult($queryStr);
6702                if (is_bool($res) && !$res) {
6703                    $db->rollbackTransaction();
6704                    return false;
6705                }
6706            }
6707        }
6708        if(!$this->setStatus($docstatus, $docmsg, $requestUser)) {
6709            $db->rollbackTransaction();
6710            return false;
6711        }
6712        $db->commitTransaction();
6713        return true;
6714
6715    } /* }}} */
6716
6717    /**
6718     * Set state of workflow assigned to the document content
6719     *
6720     * @param object $state
6721     */
6722    public function setWorkflowState($state) { /* {{{ */
6723        $db = $this->_document->getDMS()->getDB();
6724
6725        if($this->_workflow) {
6726            $queryStr = "UPDATE `tblWorkflowDocumentContent` set `state`=". $state->getID() ." WHERE `id`=". $this->_workflow['id'];
6727            if (!$db->getResult($queryStr)) {
6728                return false;
6729            }
6730            $this->_workflowState = $state;
6731            return true;
6732        }
6733        return false;
6734    } /* }}} */
6735
6736    /**
6737     * Get state of workflow assigned to the document content
6738     *
6739     * @return object/boolean an object of class SeedDMS_Core_Workflow_State
6740     *         or false in case of error, e.g. the version has not a workflow
6741     */
6742    public function getWorkflowState() { /* {{{ */
6743        $db = $this->_document->getDMS()->getDB();
6744
6745        if(!$this->_workflow)
6746            $this->getWorkflow();
6747
6748        if(!$this->_workflow)
6749            return false;
6750
6751        if (!$this->_workflowState) {
6752            $queryStr=
6753                "SELECT b.* FROM `tblWorkflowDocumentContent` a LEFT JOIN `tblWorkflowStates` b ON a.`state` = b.id WHERE a.`state` IS NOT NULL AND `a`.`id`=". $this->_workflow['id'];
6754            $recs = $db->getResultArray($queryStr);
6755            if (!$recs)
6756                return false;
6757            $this->_workflowState = new SeedDMS_Core_Workflow_State($recs[0]['id'], $recs[0]['name'], $recs[0]['maxtime'], $recs[0]['precondfunc'], $recs[0]['documentstatus']);
6758            $this->_workflowState->setDMS($this->_document->getDMS());
6759        }
6760        return $this->_workflowState;
6761    } /* }}} */
6762
6763    /**
6764     * Assign a workflow to a document content
6765     *
6766     * @param object $workflow
6767     */
6768    public function setWorkflow($workflow, $user) { /* {{{ */
6769        $db = $this->_document->getDMS()->getDB();
6770
6771        $this->getWorkflow();
6772        if($this->_workflow)
6773            return false;
6774
6775        if($workflow && is_object($workflow)) {
6776            $db->startTransaction();
6777            $initstate = $workflow->getInitState();
6778            $queryStr = "INSERT INTO `tblWorkflowDocumentContent` (`workflow`, `document`, `version`, `state`, `date`) VALUES (". $workflow->getID(). ", ". $this->_document->getID() .", ". $this->_version .", ".$initstate->getID().", ".$db->getCurrentDatetime().")";
6779            if (!$db->getResult($queryStr)) {
6780                $db->rollbackTransaction();
6781                return false;
6782            }
6783            $this->getWorkflow();
6784            if($workflow->getID() != $this->_workflow['workflow']->getID()) {
6785                $db->rollbackTransaction();
6786                return false;
6787            }
6788            if(!$this->setStatus(S_IN_WORKFLOW, "Added workflow '".$workflow->getName()."'", $user)) {
6789                $db->rollbackTransaction();
6790                return false;
6791            }
6792            $db->commitTransaction();
6793            return true;
6794        }
6795        return false;
6796    } /* }}} */
6797
6798    /**
6799     * Get workflow assigned to the document content
6800     *
6801     * The method returns the last workflow if one was assigned.
6802     * If the document version is in a sub workflow, it will have
6803     * a never date and therefore will be found first.
6804     * The methods also sets $this->_workflow['id'] and
6805     * $this->_workflow['parent']. $this->_workflow['id'] is the
6806     * id from table tblWorkflowDocumentContent which is used to
6807     * get log entries for this workflow.
6808     * This method will only get a currently running workflow in
6809     * a state. Once a
6810     * workflow has ended, the current state of the workflow was
6811     * set to null.
6812     *
6813     * @param bool $full return not just workflow but the data from
6814     *        tblWorkflowDocumentContent too
6815     * @return object/boolean an object of class SeedDMS_Core_Workflow
6816     *         or false in case of error, e.g. the version has not a workflow
6817     */
6818    public function getWorkflow($full = false) { /* {{{ */
6819        $db = $this->_document->getDMS()->getDB();
6820
6821        if (!$this->_workflow) {
6822            $queryStr=
6823                "SELECT a.`id` as `wdcid`, a.`parent`, a.`date`, b.* FROM `tblWorkflowDocumentContent` a LEFT JOIN `tblWorkflows` b ON a.`workflow` = b.`id` WHERE a.`version`='".$this->_version
6824                ."' AND a.`document` = '". $this->_document->getID() ."' "
6825                ." AND a.`state` IS NOT NULL"
6826                ." ORDER BY `date` DESC LIMIT 1";
6827            $recs = $db->getResultArray($queryStr);
6828            if (is_bool($recs) && !$recs)
6829                return false;
6830            if(!$recs)
6831                return false;
6832            $this->_workflow = array('id'=>(int)$recs[0]['wdcid'], 'parent'=>(int)$recs[0]['parent'], 'date'=>$recs[0]['date'], 'workflow'=>new SeedDMS_Core_Workflow($recs[0]['id'], $recs[0]['name'], $this->_document->getDMS()->getWorkflowState($recs[0]['initstate']), $recs[0]["layoutdata"]));
6833            $this->_workflow['workflow']->setDMS($this->_document->getDMS());
6834        }
6835        if($full)
6836            return $this->_workflow;
6837        else
6838            return $this->_workflow['workflow'];
6839    } /* }}} */
6840
6841    /**
6842     * Rewrites the complete workflow log
6843     *
6844     * Attention: this function is highly dangerous.
6845     * It removes an existing workflow log and rewrites it.
6846     * This method was added for importing an xml dump.
6847     *
6848     * @param array $workflowlog new workflow log with the newest log entry first.
6849     * @return boolean true on success, otherwise false
6850     */
6851    public function rewriteWorkflowLog($workflowlog) { /* {{{ */
6852        $db = $this->_document->getDMS()->getDB();
6853
6854        /* Get the workflowdocumentcontent */
6855        $queryStr = "SELECT `id` FROM `tblWorkflowDocumentContent` WHERE `tblWorkflowDocumentContent`.`document` = '". $this->_document->getID() ."' AND `tblWorkflowDocumentContent`.`version` = '". $this->_version ."'";
6856        $recs = $db->getResultArray($queryStr);
6857        if (is_bool($recs) && !$recs)
6858            return false;
6859        if (!$recs)
6860            return false;
6861
6862        $db->startTransaction();
6863
6864        /* First, remove the old entries */
6865        $queryStr = "DELETE FROM `tblWorkflowLog` WHERE `tblWorkflowLog`.`workflowdocumentcontent` IN (SELECT `id` FROM `tblWorkflowDocumentContent` WHERE `tblWorkflowDocumentContent`.`document` = '". $this->_document->getID() ."' AND `tblWorkflowDocumentContent`.`version` = '". $this->_version ."')";
6866        if (!$db->getResult($queryStr)) {
6867            $db->rollbackTransaction();
6868            return false;
6869        }
6870
6871        /* Second, insert the new entries */
6872        $workflowlog = array_reverse($workflowlog);
6873        foreach($workflowlog as $log) {
6874            if(!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
6875                $db->rollbackTransaction();
6876                return false;
6877            }
6878            $queryStr = "INSERT INTO `tblWorkflowLog` (`workflowdocumentcontent`, `transition`, `comment`, `date`, `userid`) ".
6879                "VALUES ('".$recs[0]['id'] ."', '".(int) $log['transition']->getID()."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".$log['user']->getID().")";
6880            if (!$db->getResult($queryStr)) {
6881                $db->rollbackTransaction();
6882                return false;
6883            }
6884        }
6885
6886        $db->commitTransaction();
6887        return true;
6888    } /* }}} */
6889
6890    /**
6891     * Restart workflow from its initial state
6892     *
6893     * @return boolean true if workflow could be restarted
6894     *         or false in case of error
6895     */
6896    public function rewindWorkflow() { /* {{{ */
6897        $db = $this->_document->getDMS()->getDB();
6898
6899        $this->getWorkflow();
6900
6901        if (!$this->_workflow) {
6902            return true;
6903        }
6904        $workflow = $this->_workflow['workflow'];
6905
6906        $db->startTransaction();
6907        $queryStr = "DELETE from `tblWorkflowLog` WHERE `workflowdocumentcontent` = ".$this->_workflow['id'];
6908        if (!$db->getResult($queryStr)) {
6909            $db->rollbackTransaction();
6910            return false;
6911        }
6912
6913        $this->setWorkflowState($workflow->getInitState());
6914        $db->commitTransaction();
6915
6916        return true;
6917    } /* }}} */
6918
6919    /**
6920     * Remove workflow
6921     *
6922     * Fully removing a workflow including entries in the workflow log is
6923     * only allowed if the workflow is still its initial state.
6924     * At a later point of time only unlinking the document from the
6925     * workflow is allowed. It will keep any log entries and set the state
6926     * to NULL.
6927     * A workflow is unlinked from a document when enterNextState()
6928     * succeeds.
6929     *
6930     * @param object $user user doing initiating the removal
6931     * @param boolean $unlink if true, just unlink the workflow from the
6932     *        document but do not remove the workflow log. The $unlink
6933     *        flag has been added to detach the workflow from the document
6934     *        when it has reached a valid end state
6935              (see SeedDMS_Core_DocumentContent::enterNextState())
6936     * @return boolean true if workflow could be removed
6937     *         or false in case of error
6938     */
6939    public function removeWorkflow($user, $unlink=false) { /* {{{ */
6940        $db = $this->_document->getDMS()->getDB();
6941
6942        $this->getWorkflow();
6943
6944        if (!$this->_workflow) {
6945            return true;
6946        }
6947
6948        $workflow = $this->_workflow['workflow'];
6949
6950        /* A workflow should always be in a state, but in case it isn't, the
6951         * at least allow to remove the workflow.
6952         */
6953        $currentstate = $this->getWorkflowState();
6954        if(!$currentstate || SeedDMS_Core_DMS::checkIfEqual($workflow->getInitState(), $currentstate) || $unlink == true) {
6955            $db->startTransaction();
6956            if($unlink) {
6957                $queryStr=
6958                    "UPDATE `tblWorkflowDocumentContent` SET `state` = NULL WHERE `id`=".$this->_workflow['id'];
6959                if (!$db->getResult($queryStr)) {
6960                    $db->rollbackTransaction();
6961                    return false;
6962                }
6963            } else {
6964                $queryStr=
6965                    "DELETE FROM `tblWorkflowDocumentContent` WHERE `id`=".$this->_workflow['id'];
6966                if (!$db->getResult($queryStr)) {
6967                    $db->rollbackTransaction();
6968                    return false;
6969                }
6970                /* will be deleted automatically when tblWorkflowDocumentContent is deleted
6971                $queryStr=
6972                    "DELETE FROM `tblWorkflowLog` WHERE "
6973                    ."`version`='".$this->_version."' "
6974                    ." AND `document` = '". $this->_document->getID() ."' "
6975                    ." AND `workflow` = '". $workflow->getID() ."' ";
6976                if (!$db->getResult($queryStr)) {
6977                    $db->rollbackTransaction();
6978                    return false;
6979                }
6980                 */
6981            }
6982            $this->_workflow = null;
6983            $this->_workflowState = null;
6984            $this->verifyStatus(false, $user, 'Workflow removed');
6985            $db->commitTransaction();
6986        }
6987
6988        return true;
6989    } /* }}} */
6990
6991    /**
6992     * Run a sub workflow
6993     *
6994     * @param object $subworkflow
6995     */
6996    public function getParentWorkflow() { /* {{{ */
6997        $db = $this->_document->getDMS()->getDB();
6998
6999        /* document content must be in a workflow */
7000        $this->getWorkflow();
7001        if(!$this->_workflow)
7002            return false;
7003
7004        if(!$this->_workflow['parent'])
7005            return false;
7006
7007        $queryStr=
7008            "SELECT * FROM `tblWorkflowDocumentContent` WHERE `parent`=".$this->_workflow['parent'];
7009        $recs = $db->getResultArray($queryStr);
7010        if (is_bool($recs) && !$recs)
7011            return false;
7012        if(!$recs)
7013            return false;
7014
7015        if($recs[0]['workflow'])
7016            return $this->_document->getDMS()->getWorkflow((int)$recs[0]['workflow']);
7017
7018        return false;
7019    } /* }}} */
7020
7021    /**
7022     * Run a sub workflow
7023     *
7024     * @param object $subworkflow
7025     */
7026    public function runSubWorkflow($subworkflow) { /* {{{ */
7027        $db = $this->_document->getDMS()->getDB();
7028
7029        /* document content must be in a workflow */
7030        $this->getWorkflow();
7031        if(!$this->_workflow)
7032            return false;
7033
7034        /* The current workflow state must match the sub workflows initial state */
7035        if($subworkflow->getInitState()->getID() != $this->_workflowState->getID())
7036            return false;
7037
7038        if($subworkflow) {
7039            $initstate = $subworkflow->getInitState();
7040            $queryStr = "INSERT INTO `tblWorkflowDocumentContent` (`parent`, `workflow`, `document`, `version`, `state`, `date`) VALUES (". $this->_workflow['id']. ", ". $subworkflow->getID(). ", ". $this->_document->getID() .", ". $this->_version .", ".$initstate->getID().", ".$db->getCurrentDatetime().")";
7041            if (!$db->getResult($queryStr)) {
7042                return false;
7043            }
7044            $this->_workflow = array('id'=>$db->getInsertID('tblWorkflowDocumentContent'),  'parent'=>$this->_workflow['id'], 'workflow'=>$subworkflow);
7045            return true;
7046        }
7047        return true;
7048    } /* }}} */
7049
7050    /**
7051     * Return from sub workflow to parent workflow.
7052     * The method will trigger the given transition
7053     *
7054     * FIXME: Needs much better checking if this is allowed
7055     *
7056     * @param object $user intiating the return
7057     * @param object $transtion to trigger
7058     * @param string comment for the transition trigger
7059     */
7060    public function returnFromSubWorkflow($user, $transition=null, $comment='') { /* {{{ */
7061        $db = $this->_document->getDMS()->getDB();
7062
7063        /* document content must be in a workflow */
7064        $this->getWorkflow();
7065        if(!$this->_workflow)
7066            return false;
7067
7068        if ($this->_workflow) {
7069            $db->startTransaction();
7070
7071            $queryStr = "UPDATE `tblWorkflowDocumentContent` SET `state` = NULL WHERE `id` = '" . $this->_workflow['id']."'";
7072            if (!$db->getResult($queryStr)) {
7073                $db->rollbackTransaction();
7074                return false;
7075            }
7076
7077            /* Calling getWorkflow() should find the parent workflow, better check */
7078            $parent = $this->_workflow['parent'];
7079            $this->_workflow = null;
7080            $this->getWorkflow();
7081            if($this->_workflow['id'] != $parent) {
7082                $db->rollbackTransaction();
7083                return false;
7084            }
7085
7086            if($transition) {
7087                if(false === $this->triggerWorkflowTransition($user, $transition, $comment)) {
7088                    $db->rollbackTransaction();
7089                    return false;
7090                }
7091            }
7092
7093            $db->commitTransaction();
7094        }
7095        return $this->_workflow['workflow'];
7096    } /* }}} */
7097
7098    /**
7099     * Check if the user is allowed to trigger the transition
7100     * A user is allowed if either the user itself or
7101     * a group of which the user is a member of is registered for
7102     * triggering a transition. This method does not change the workflow
7103     * state of the document content.
7104     *
7105     * @param object $user
7106     * @return boolean true if user may trigger transaction
7107     */
7108    public function triggerWorkflowTransitionIsAllowed($user, $transition) { /* {{{ */
7109        $db = $this->_document->getDMS()->getDB();
7110
7111        if(!$this->_workflow)
7112            $this->getWorkflow();
7113
7114        if(!$this->_workflow)
7115            return false;
7116
7117        if(!$this->_workflowState)
7118            $this->getWorkflowState();
7119
7120        /* Check if the user has already triggered the transition */
7121        $queryStr=
7122            "SELECT * FROM `tblWorkflowLog` WHERE `workflowdocumentcontent` = ".$this->_workflow['id']." AND userid = ".$user->getID();
7123        $queryStr .= " AND `transition` = ".$transition->getID();
7124        $resArr = $db->getResultArray($queryStr);
7125        if (is_bool($resArr) && !$resArr)
7126            return false;
7127
7128        if(count($resArr))
7129            return false;
7130
7131        /* Get all transition users allowed to trigger the transition */
7132        $transusers = $transition->getUsers();
7133        if($transusers) {
7134            foreach($transusers as $transuser) {
7135                if($user->getID() == $transuser->getUser()->getID())
7136                    return true;
7137            }
7138        }
7139
7140        /* Get all transition groups whose members are allowed to trigger
7141         * the transition */
7142        $transgroups = $transition->getGroups();
7143        if($transgroups) {
7144            foreach($transgroups as $transgroup) {
7145                $group = $transgroup->getGroup();
7146                if($group->isMember($user))
7147                    return true;
7148            }
7149        }
7150
7151        return false;
7152    } /* }}} */
7153
7154    /**
7155     * Check if all conditions are met to change the workflow state
7156     * of a document content (run the transition).
7157     * The conditions are met if all explicitly set users and a sufficient
7158     * number of users of the groups have acknowledged the content.
7159     *
7160     * @return boolean true if transaction maybe executed
7161     */
7162    public function executeWorkflowTransitionIsAllowed($transition) { /* {{{ */
7163        if(!$this->_workflow)
7164            $this->getWorkflow();
7165
7166        if(!$this->_workflow)
7167            return false;
7168
7169        if(!$this->_workflowState)
7170            $this->getWorkflowState();
7171
7172        /* Get the Log of transition triggers */
7173        $entries = $this->getWorkflowLog($transition);
7174        if(!$entries)
7175            return false;
7176
7177        /* Get all transition users allowed to trigger the transition
7178         * $allowedusers is a list of all users allowed to trigger the
7179         * transition
7180         */
7181        $transusers = $transition->getUsers();
7182        $allowedusers = array();
7183        foreach($transusers as $transuser) {
7184            $a = $transuser->getUser();
7185            $allowedusers[$a->getID()] = $a;
7186        }
7187
7188        /* Get all transition groups whose members are allowed to trigger
7189         * the transition */
7190        $transgroups = $transition->getGroups();
7191        foreach($entries as $entry) {
7192            $loguser = $entry->getUser();
7193            /* Unset each allowed user if it was found in the log */
7194            if(isset($allowedusers[$loguser->getID()]))
7195                unset($allowedusers[$loguser->getID()]);
7196            /* Also check groups if required. Count the group membership of
7197             * each user in the log in the array $gg
7198             */
7199            if($transgroups) {
7200                $loggroups = $loguser->getGroups();
7201                foreach($loggroups as $loggroup) {
7202                    if(!isset($gg[$loggroup->getID()]))
7203                        $gg[$loggroup->getID()] = 1;
7204                    else
7205                        $gg[$loggroup->getID()]++;
7206                }
7207            }
7208        }
7209        /* If there are allowed users left, then there some users still
7210         * need to trigger the transition.
7211         */
7212        if($allowedusers)
7213            return false;
7214
7215        if($transgroups) {
7216            foreach($transgroups as $transgroup) {
7217                $group = $transgroup->getGroup();
7218                $minusers = $transgroup->getNumOfUsers();
7219                if(!isset($gg[$group->getID()]))
7220                    return false;
7221                if($gg[$group->getID()] < $minusers)
7222                    return false;
7223            }
7224        }
7225        return true;
7226    } /* }}} */
7227
7228    /**
7229     * Trigger transition
7230     *
7231     * This method will be deprecated
7232     *
7233     * The method will first check if the user is allowed to trigger the
7234     * transition. If the user is allowed, an entry in the workflow log
7235     * will be added, which is later used to check if the transition
7236     * can actually be processed. The method will finally call
7237     * executeWorkflowTransitionIsAllowed() which checks all log entries
7238     * and does the transitions post function if all users and groups have
7239     * triggered the transition. Finally enterNextState() is called which
7240     * will try to enter the next state.
7241     *
7242     * @param object $user
7243     * @param object $transition
7244     * @param string $comment user comment
7245     * @return boolean/object next state if transition could be triggered and
7246     *         then next state could be entered,
7247     *         true if the transition could just be triggered or
7248     *         false in case of an error
7249     */
7250    public function triggerWorkflowTransition($user, $transition, $comment='') { /* {{{ */
7251        $db = $this->_document->getDMS()->getDB();
7252
7253        if(!$this->_workflow)
7254            $this->getWorkflow();
7255
7256        if(!$this->_workflow)
7257            return false;
7258
7259        if(!$this->_workflowState)
7260            $this->getWorkflowState();
7261
7262        if(!$this->_workflowState)
7263            return false;
7264
7265        /* Check if the user is allowed to trigger the transition.
7266         */
7267        if(!$this->triggerWorkflowTransitionIsAllowed($user, $transition))
7268            return false;
7269
7270        $queryStr = "INSERT INTO `tblWorkflowLog` (`workflowdocumentcontent`, `userid`, `transition`, `date`, `comment`) VALUES (".$this->_workflow['id'].", ".(int) $user->getID(). ", ".(int) $transition->getID().", ".$db->getCurrentDatetime().", ".$db->qstr($comment).")";
7271        if (!$db->getResult($queryStr))
7272            return false;
7273
7274        /* Check if this transition is processed. Run the post function in
7275         * that case. A transition is processed when all users and groups
7276         * have triggered it.
7277         */
7278        if($this->executeWorkflowTransitionIsAllowed($transition)) {
7279            /* run post function of transition */
7280//            echo "run post function of transition ".$transition->getID()."<br />";
7281        }
7282
7283        /* Go into the next state. This will only succeed if the pre condition
7284         * function of that states succeeds.
7285         */
7286        $nextstate = $transition->getNextState();
7287        if($this->enterNextState($user, $nextstate)) {
7288            return $nextstate;
7289        }
7290        return true;
7291
7292    } /* }}} */
7293
7294    /**
7295     * Enter next state of workflow if possible
7296     *
7297     * The method will check if one of the following states in the workflow
7298     * can be reached.
7299     * It does it by running
7300     * the precondition function of that state. The precondition function
7301     * gets a list of all transitions leading to the state. It will
7302     * determine, whether the transitions has been triggered and if that
7303     * is sufficient to enter the next state. If no pre condition function
7304     * is set, then 1 of n transtions are enough to enter the next state.
7305     *
7306     * If moving in the next state is possible and this state has a
7307     * corresponding document state, then the document state will be
7308     * updated and the workflow will be detached from the document.
7309     *
7310     * @param object $user
7311     * @param object $nextstate
7312     * @return boolean true if the state could be reached
7313     *         false if not
7314     */
7315    public function enterNextState($user, $nextstate) { /* {{{ */
7316
7317            /* run the pre condition of the next state. If it is not set
7318             * the next state will be reached if one of the transitions
7319             * leading to the given state can be processed.
7320             */
7321            if($nextstate->getPreCondFunc() == '') {
7322                $workflow = $this->_workflow['workflow'];
7323                $transitions = $workflow->getPreviousTransitions($nextstate);
7324                foreach($transitions as $transition) {
7325//                echo "transition ".$transition->getID()." led to state ".$nextstate->getName()."<br />";
7326                    if($this->executeWorkflowTransitionIsAllowed($transition)) {
7327//                    echo "stepping into next state<br />";
7328                        $this->setWorkflowState($nextstate);
7329
7330                        /* Check if the new workflow state has a mapping into a
7331                         * document state. If yes, set the document state will
7332                         * be updated and the workflow will be removed from the
7333                         * document.
7334                         */
7335                        $docstate = $nextstate->getDocumentStatus();
7336                        if($docstate == S_RELEASED || $docstate == S_REJECTED) {
7337                            $this->setStatus($docstate, "Workflow has ended", $user);
7338                            /* Detach the workflow from the document, but keep the
7339                             * workflow log
7340                             */
7341                            $this->removeWorkflow($user, true);
7342                            return true ;
7343                        }
7344
7345                        /* make sure the users and groups allowed to trigger the next
7346                         * transitions are also allowed to read the document
7347                         */
7348                        $transitions = $workflow->getNextTransitions($nextstate);
7349                        foreach($transitions as $tran) {
7350//                            echo "checking access for users/groups allowed to trigger transition ".$tran->getID()."<br />";
7351                            $transusers = $tran->getUsers();
7352                            foreach($transusers as $transuser) {
7353                                $u = $transuser->getUser();
7354//                                echo $u->getFullName()."<br />";
7355                                if($this->_document->getAccessMode($u) < M_READ) {
7356                                    $this->_document->addAccess(M_READ, $u->getID(), 1);
7357//                                    echo "granted read access<br />";
7358                                } else {
7359//                                    echo "has already access<br />";
7360                                }
7361                            }
7362                            $transgroups = $tran->getGroups();
7363                            foreach($transgroups as $transgroup) {
7364                                $g = $transgroup->getGroup();
7365//                                echo $g->getName()."<br />";
7366                                if ($this->_document->getGroupAccessMode($g) < M_READ) {
7367                                    $this->_document->addAccess(M_READ, $g->getID(), 0);
7368//                                    echo "granted read access<br />";
7369                                } else {
7370//                                    echo "has already access<br />";
7371                                }
7372                            }
7373                        }
7374                        return(true);
7375                    } else {
7376//                        echo "transition not ready for process now<br />";
7377                    }
7378                }
7379                return false;
7380            } else {
7381            }
7382
7383    } /* }}} */
7384
7385    /**
7386     * Get the so far logged operations on the document content within the
7387     * workflow. If the document content is currently in a workflow and
7388     * a transition is passed, then the
7389     * log entries will be restricted on the workflow and returned as a one
7390     * dimensional list. Without a running workflow the log entries of
7391     * all workflows in the past are returned grouped by workflow.
7392     * This result is a two dimensional array. The keys of the first
7393     * dimension are the ids used in table tblWorkflowDocumentContent.
7394     * If only the logs of last workflow run are of interesst, then just
7395     * take the last element of the returned array.
7396     *
7397     * Example: A workflow was started for a document content.
7398     * This will add an entry in tblWorkflowDocumentContent whose state is set
7399     * to the initial state of the workflow and a new autoinc id, e.g. with id 45
7400     * Once any step in the workflow was triggered, the table tblWorkflowLog will
7401     * have an entry for workflowdocumentcontent=45.
7402     * Retrieving the workflow log as long the document is still in the workflow
7403     * will return the log entries for the current workflow. In this particular
7404     * case it will be an array with one log entry.
7405     * Once the workflow has ended this method will still return the log entries
7406     * but in a 2-dimensional array with the first dimension set to 45.
7407     *
7408     * The same document version can be run through the same or a different
7409     * workflow again which will lead to a new entry in
7410     * tblWorkflowDocumentContent, e.g. with id 46.  Getting the log entries
7411     * while the content is still in the workflow will return only those entries
7412     * for the current workflow. Once the workflow has ended, this methods
7413     * returns a 2-dimensional array with two elements in the first dimension.
7414     * One for key 45 and another one for key 46.
7415     *
7416     * @return array list of objects
7417     */
7418    public function getWorkflowLog($transition = null) { /* {{{ */
7419        $db = $this->_document->getDMS()->getDB();
7420
7421        if(!$this->_workflow)
7422            $this->getWorkflow();
7423
7424        $queryStr=
7425            "SELECT `a`.`id`, `a`.`userid`, `a`.`transition`, `a`.`date`, `a`.`comment`, `a`.`workflowdocumentcontent`, `b`.`version`, `b`.`document`, `b`.`workflow` FROM `tblWorkflowLog` `a` LEFT JOIN `tblWorkflowDocumentContent` `b` ON `a`.`workflowdocumentcontent` = `b`.`id` WHERE `b`.`version`='".$this->_version ."' AND `b`.`document` = '". $this->_document->getID() ."'"; // AND `workflow` = ". $this->_workflow->getID();
7426        if($transition) {
7427            $queryStr .= " AND `a`.`transition` = ".$transition->getID();
7428        }
7429        if($this->_workflow)
7430            $queryStr .= " AND `a`.`workflowdocumentcontent` = ".$this->_workflow['id'];
7431        $queryStr .= " ORDER BY `a`.`date`";
7432        $resArr = $db->getResultArray($queryStr);
7433        if (is_bool($resArr) && !$resArr)
7434            return false;
7435
7436        $workflowlogs = array();
7437        for ($i = 0; $i < count($resArr); $i++) {
7438            $workflow = $this->_document->getDMS()->getWorkflow($resArr[$i]["workflow"]);
7439            $workflowlog = new SeedDMS_Core_Workflow_Log($resArr[$i]["id"], $this->_document->getDMS()->getDocument($resArr[$i]["document"]), $resArr[$i]["version"], $workflow, $this->_document->getDMS()->getUser($resArr[$i]["userid"]), $workflow->getTransition($resArr[$i]["transition"]), $resArr[$i]["date"], $resArr[$i]["comment"]);
7440            $workflowlog->setDMS($this);
7441            if($this->_workflow)
7442                $workflowlogs[] = $workflowlog;
7443            else
7444                $workflowlogs[$resArr[$i]["workflowdocumentcontent"]][] = $workflowlog;
7445        }
7446
7447        return $workflowlogs;
7448    } /* }}} */
7449
7450    /**
7451     * Get the latest workflow log entry for the document content within the
7452     * workflow. Even after finishing the workflow (when the document content
7453     * does not have a workflow set anymore) this function returns the last
7454     * log entry.
7455     *
7456     * @return object
7457     */
7458    public function getLastWorkflowLog() { /* {{{ */
7459        $db = $this->_document->getDMS()->getDB();
7460
7461/*
7462        if(!$this->_workflow)
7463            $this->getWorkflow();
7464
7465        if(!$this->_workflow)
7466            return false;
7467 */
7468        $queryStr=
7469            "SELECT `a`.*, `b`.`workflow`, `b`.`document`, `b`.`version` FROM `tblWorkflowLog` `a` LEFT JOIN `tblWorkflowDocumentContent` `b` ON `a`.`workflowdocumentcontent` = `b`.`id` WHERE `b`.`version`='".$this->_version ."' AND `b`.`document` = '". $this->_document->getID() ."'";
7470        $queryStr .= " ORDER BY `id` DESC LIMIT 1";
7471        $resArr = $db->getResultArray($queryStr);
7472        if (is_bool($resArr) && !$resArr)
7473            return false;
7474
7475        $i = 0;
7476        $workflow = $this->_document->getDMS()->getWorkflow($resArr[$i]["workflow"]);
7477        $workflowlog = new SeedDMS_Core_Workflow_Log($resArr[$i]["id"], $this->_document->getDMS()->getDocument($resArr[$i]["document"]), $resArr[$i]["version"], $workflow, $this->_document->getDMS()->getUser($resArr[$i]["userid"]), $workflow->getTransition($resArr[$i]["transition"]), $resArr[$i]["date"], $resArr[$i]["comment"]);
7478        $workflowlog->setDMS($this);
7479
7480        return $workflowlog;
7481    } /* }}} */
7482
7483    /**
7484     * Check if the document content needs an action by a user
7485     *
7486     * This method will return true if document content is in a transition
7487     * which can be triggered by the given user.
7488     *
7489     * @param SeedDMS_Core_User $user
7490     * @return boolean true is action is needed
7491     */
7492    public function needsWorkflowAction($user) { /* {{{ */
7493        $needwkflaction = false;
7494        if($this->_workflow) {
7495            $workflow = $this->_workflow['workflow'];
7496            if (!$this->_workflowState)
7497                $this->getWorkflowState();
7498            $workflowstate = $this->_workflowState;
7499            if($transitions = $workflow->getNextTransitions($workflowstate)) {
7500                foreach($transitions as $transition) {
7501                    if($this->triggerWorkflowTransitionIsAllowed($user, $transition)) {
7502                        $needwkflaction = true;
7503                    }
7504                }
7505            }
7506        }
7507        return $needwkflaction;
7508    } /* }}} */
7509
7510    /**
7511     * Checks the internal data of the document version and repairs it.
7512     * Currently, this function only repairs a missing filetype
7513     *
7514     * @return boolean true on success, otherwise false
7515     */
7516    public function repair() { /* {{{ */
7517        $dms = $this->_document->getDMS();
7518        $db = $this->_dms->getDB();
7519
7520        if(SeedDMS_Core_File::file_exists($this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType)) {
7521            if(strlen($this->_fileType) < 2) {
7522                switch($this->_mimeType) {
7523                case "application/pdf":
7524                case "image/png":
7525                case "image/gif":
7526                case "image/jpg":
7527                    $expect = substr($this->_mimeType, -3, 3);
7528                    if($this->_fileType != '.'.$expect) {
7529                        $db->startTransaction();
7530                        $queryStr = "UPDATE `tblDocumentContent` SET `fileType`='.".$expect."' WHERE `id` = ". $this->_id;
7531                        $res = $db->getResult($queryStr);
7532                        if ($res) {
7533                            if(!SeedDMS_Core_File::renameFile($this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType, $this->_dms->contentDir.$this->_document->getDir() . $this->_version . '.' . $expect)) {
7534                                $db->rollbackTransaction();
7535                            } else {
7536                                $db->commitTransaction();
7537                            }
7538                        } else {
7539                            $db->rollbackTransaction();
7540                        }
7541                    }
7542                    break;
7543                }
7544            }
7545        } elseif(SeedDMS_Core_File::file_exists($this->_document->getDir() . $this->_version . '.')) {
7546            echo "no file";
7547        } else {
7548            echo $this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType;
7549        }
7550        return true;
7551    } /* }}} */
7552
7553} /* }}} */
7554
7555
7556/**
7557 * Class to represent a link between two document
7558 *
7559 * Document links are to establish a reference from one document to
7560 * another document. The owner of the document link may not be the same
7561 * as the owner of one of the documents.
7562 * Use {@see SeedDMS_Core_Document::addDocumentLink()} to add a reference
7563 * to another document.
7564 *
7565 * @category   DMS
7566 * @package    SeedDMS_Core
7567 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
7568 *             Uwe Steinmann <uwe@steinmann.cx>
7569 * @copyright  Copyright (C) 2002-2005 Markus Westphal,
7570 *             2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli,
7571 *             2010-2024 Uwe Steinmann
7572 * @version    Release: @package_version@
7573 */
7574class SeedDMS_Core_DocumentLink { /* {{{ */
7575    /**
7576     * @var integer internal id of document link
7577     */
7578    protected $_id;
7579
7580    /**
7581     * @var SeedDMS_Core_Document reference to document this link belongs to
7582     */
7583    protected $_document;
7584
7585    /**
7586     * @var object reference to target document this link points to
7587     */
7588    protected $_target;
7589
7590    /**
7591     * @var integer id of user who is the owner of this link
7592     */
7593    protected $_userID;
7594
7595    /**
7596     * @var object $_user user who is the owner of this link
7597     */
7598    protected $_user;
7599
7600    /**
7601     * @var integer 1 if this link is public, or 0 if is only visible to the owner
7602     */
7603    protected $_public;
7604
7605    /**
7606     * SeedDMS_Core_DocumentLink constructor.
7607     * @param $id
7608     * @param $document
7609     * @param $target
7610     * @param $userID
7611     * @param $public
7612     */
7613    public function __construct($id, $document, $target, $userID, $public) {
7614        $this->_id = $id;
7615        $this->_document = $document;
7616        $this->_target = $target;
7617        $this->_userID = $userID;
7618        $this->_user = null;
7619        $this->_public = $public ? true : false;
7620    }
7621
7622    /**
7623     * Check if this object is of type 'documentlink'.
7624     *
7625     * @param string $type type of object
7626     */
7627    public function isType($type) { /* {{{ */
7628        return $type == 'documentlink';
7629    } /* }}} */
7630
7631    /**
7632     * @return int
7633     */
7634    public function getID() { return $this->_id; }
7635
7636    /**
7637     * @return SeedDMS_Core_Document
7638     */
7639    public function getDocument() {
7640        return $this->_document;
7641    }
7642
7643    /**
7644     * @return object
7645     */
7646    public function getTarget() {
7647        return $this->_target;
7648    }
7649
7650    /**
7651     * @return bool|SeedDMS_Core_User
7652     */
7653    public function getUser() {
7654        if (!isset($this->_user)) {
7655            $this->_user = $this->_document->getDMS()->getUser($this->_userID);
7656        }
7657        return $this->_user;
7658    }
7659
7660    /**
7661     * @return int
7662     */
7663    public function isPublic() { return $this->_public; }
7664
7665    /**
7666     * Returns the access mode similar to a document
7667     *
7668     * There is no real access mode for document links, so this is just
7669     * another way to add more access restrictions than the default restrictions.
7670     * It is only called for public document links, not accessed by the owner
7671     * or the administrator.
7672     *
7673     * @param SeedDMS_Core_User $u user
7674     * @param $source
7675     * @param $target
7676     * @return int either M_NONE or M_READ
7677     */
7678    public function getAccessMode($u, $source, $target) { /* {{{ */
7679        $dms = $this->_document->getDMS();
7680
7681        /* Check if 'onCheckAccessDocumentLink' callback is set */
7682        if(isset($dms->callbacks['onCheckAccessDocumentLink'])) {
7683            foreach($dms->callbacks['onCheckAccessDocumentLink'] as $callback) {
7684                if(($ret = call_user_func($callback[0], $callback[1], $this, $u, $source, $target)) > 0) {
7685                    return $ret;
7686                }
7687            }
7688        }
7689
7690        return M_READ;
7691    } /* }}} */
7692
7693} /* }}} */
7694
7695/**
7696 * Class to represent a file attached to a document
7697 *
7698 * Beside the regular document content arbitrary files can be attached
7699 * to a document. This is a similar concept as attaching files to emails.
7700 * The owner of the attached file and the document may not be the same.
7701 * Use {@see SeedDMS_Core_Document::addDocumentFile()} to attach a file.
7702 *
7703 * @category   DMS
7704 * @package    SeedDMS_Core
7705 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
7706 *             Uwe Steinmann <uwe@steinmann.cx>
7707 * @copyright  Copyright (C) 2002-2005 Markus Westphal,
7708 *             2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli,
7709 *             2010-2024 Uwe Steinmann
7710 * @version    Release: @package_version@
7711 */
7712class SeedDMS_Core_DocumentFile { /* {{{ */
7713    /**
7714     * @var integer internal id of document file
7715     */
7716    protected $_id;
7717
7718    /**
7719     * @var SeedDMS_Core_Document reference to document this file belongs to
7720     */
7721    protected $_document;
7722
7723    /**
7724     * @var integer id of user who is the owner of this link
7725     */
7726    protected $_userID;
7727
7728    /**
7729     * @var string comment for the attached file
7730     */
7731    protected $_comment;
7732
7733    /**
7734     * @var string date when the file was attached
7735     */
7736    protected $_date;
7737
7738    /**
7739     * @var integer version of document this file is attached to
7740     */
7741    protected $_version;
7742
7743    /**
7744     * @var integer 1 if this link is public, or 0 if is only visible to the owner
7745     */
7746    protected $_public;
7747
7748    /**
7749     * @var string directory where the file is stored. This is the
7750     * document id with a proceding '/'.
7751     * FIXME: looks like this isn't used anymore. The file path is
7752     * constructed by getPath()
7753     */
7754    protected $_dir;
7755
7756    /**
7757     * @var string extension of the original file name with a leading '.'
7758     */
7759    protected $_fileType;
7760
7761    /**
7762     * @var string mime type of the file
7763     */
7764    protected $_mimeType;
7765
7766    /**
7767     * @var string name of the file that was originally uploaded
7768     */
7769    protected $_orgFileName;
7770
7771    /**
7772     * @var string name of the file as given by the user
7773     */
7774    protected $_name;
7775
7776    /**
7777     * SeedDMS_Core_DocumentFile constructor.
7778     * @param $id
7779     * @param $document
7780     * @param $userID
7781     * @param $comment
7782     * @param $date
7783     * @param $dir
7784     * @param $fileType
7785     * @param $mimeType
7786     * @param $orgFileName
7787     * @param $name
7788     * @param $version
7789     * @param $public
7790     */
7791    public function __construct($id, $document, $userID, $comment, $date, $dir, $fileType, $mimeType, $orgFileName,$name,$version,$public) {
7792        $this->_id = $id;
7793        $this->_document = $document;
7794        $this->_userID = $userID;
7795        $this->_comment = $comment;
7796        $this->_date = $date;
7797        $this->_dir = $dir;
7798        $this->_fileType = $fileType;
7799        $this->_mimeType = $mimeType;
7800        $this->_orgFileName = $orgFileName;
7801        $this->_name = $name;
7802        $this->_version = $version;
7803        $this->_public = $public ? true : false;
7804    }
7805
7806    /**
7807     * Check if this object is of type 'documentfile'.
7808     *
7809     * @param string $type type of object
7810     */
7811    public function isType($type) { /* {{{ */
7812        return $type == 'documentfile';
7813    } /* }}} */
7814
7815    /**
7816     * @return int
7817     */
7818    public function getID() { return $this->_id; }
7819
7820    /**
7821     * @return SeedDMS_Core_Document
7822     */
7823    public function getDocument() { return $this->_document; }
7824
7825    /**
7826     * @return int
7827     */
7828    public function getUserID() { return $this->_userID; }
7829
7830    /**
7831     * @return string
7832     */
7833    public function getComment() { return $this->_comment; }
7834
7835    /*
7836     * Set the comment of the document file
7837     *
7838     * @param string $newComment string new comment of document
7839     */
7840    public function setComment($newComment) { /* {{{ */
7841        $db = $this->_document->getDMS()->getDB();
7842
7843        $queryStr = "UPDATE `tblDocumentFiles` SET `comment` = ".$db->qstr($newComment)." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id;
7844        if (!$db->getResult($queryStr))
7845            return false;
7846
7847        $this->_comment = $newComment;
7848        return true;
7849    } /* }}} */
7850
7851    /**
7852     * @return string
7853     */
7854    public function getDate() { return $this->_date; }
7855
7856    /**
7857     * Set creation date of the document file
7858     *
7859     * @param integer $date timestamp of creation date. If false then set it
7860     * to the current timestamp
7861     * @return boolean true on success
7862     */
7863    public function setDate($date=null) { /* {{{ */
7864        $db = $this->_document->getDMS()->getDB();
7865
7866        if(!$date)
7867            $date = time();
7868        else {
7869            if(!is_numeric($date))
7870                return false;
7871        }
7872
7873        $queryStr = "UPDATE `tblDocumentFiles` SET `date` = " . (int) $date . " WHERE `id` = ". $this->_id;
7874        if (!$db->getResult($queryStr))
7875            return false;
7876        $this->_date = $date;
7877        return true;
7878    } /* }}} */
7879
7880    /**
7881     * @return string
7882     */
7883    public function getDir() { return $this->_dir; }
7884
7885    /**
7886     * @return string
7887     */
7888    public function getFileType() { return $this->_fileType; }
7889
7890    /**
7891     * @return string
7892     */
7893    public function getMimeType() { return $this->_mimeType; }
7894
7895    /**
7896     * @return string
7897     */
7898    public function getOriginalFileName() { return $this->_orgFileName; }
7899
7900    /**
7901     * @return string
7902     */
7903    public function getName() { return $this->_name; }
7904
7905    /*
7906     * Set the name of the document file
7907     *
7908     * @param $newComment string new name of document
7909     */
7910    public function setName($newName) { /* {{{ */
7911        $db = $this->_document->getDMS()->getDB();
7912
7913        $queryStr = "UPDATE `tblDocumentFiles` SET `name` = ".$db->qstr($newName)." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id;
7914        if (!$db->getResult($queryStr))
7915            return false;
7916
7917        $this->_name = $newName;
7918
7919        return true;
7920    } /* }}} */
7921
7922    /**
7923     * @return bool|SeedDMS_Core_User
7924     */
7925    public function getUser() {
7926        if (!isset($this->_user))
7927            $this->_user = $this->_document->getDMS()->getUser($this->_userID);
7928        return $this->_user;
7929    }
7930
7931    /**
7932     * @return string
7933     */
7934    public function getPath() {
7935        return $this->_document->getDir() . "f" .$this->_id . $this->_fileType;
7936    }
7937
7938    /**
7939     * @return int
7940     */
7941    public function getVersion() { return $this->_version; }
7942
7943    /*
7944     * Set the version of the document file
7945     *
7946     * @param $newComment string new version of document
7947     */
7948    public function setVersion($newVersion) { /* {{{ */
7949        $db = $this->_document->getDMS()->getDB();
7950
7951        if(!is_numeric($newVersion) && $newVersion != '')
7952            return false;
7953
7954        $queryStr = "UPDATE `tblDocumentFiles` SET `version` = ".(int) $newVersion." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id;
7955        if (!$db->getResult($queryStr))
7956            return false;
7957
7958        $this->_version = (int) $newVersion;
7959        return true;
7960    } /* }}} */
7961
7962    /**
7963     * @return int
7964     */
7965    public function isPublic() { return $this->_public; }
7966
7967    /*
7968     * Set the public flag of the document file
7969     *
7970     * @param $newComment string new comment of document
7971     */
7972    public function setPublic($newPublic) { /* {{{ */
7973        $db = $this->_document->getDMS()->getDB();
7974
7975        $queryStr = "UPDATE `tblDocumentFiles` SET `public` = ".($newPublic ? 1 : 0)." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id;
7976        if (!$db->getResult($queryStr))
7977            return false;
7978
7979        $this->_public = $newPublic ? true : false;
7980        return true;
7981    } /* }}} */
7982
7983    /**
7984     * Returns the access mode similar to a document
7985     *
7986     * There is no real access mode for document files, so this is just
7987     * another way to add more access restrictions than the default restrictions.
7988     * It is only called for public document files, not accessed by the owner
7989     * or the administrator.
7990     *
7991     * @param object $u user
7992     * @return integer either M_NONE or M_READ
7993     */
7994    public function getAccessMode($u) { /* {{{ */
7995        $dms = $this->_document->getDMS();
7996
7997        /* Check if 'onCheckAccessDocumentLink' callback is set */
7998        if(isset($this->_dms->callbacks['onCheckAccessDocumentFile'])) {
7999            foreach($this->_dms->callbacks['onCheckAccessDocumentFile'] as $callback) {
8000                if(($ret = call_user_func($callback[0], $callback[1], $this, $u)) > 0) {
8001                    return $ret;
8002                }
8003            }
8004        }
8005
8006        return M_READ;
8007    } /* }}} */
8008
8009} /* }}} */
8010
8011//
8012// Perhaps not the cleanest object ever devised, it exists to encapsulate all
8013// of the data generated during the addition of new content to the database.
8014// The object stores a copy of the new DocumentContent object, the newly assigned
8015// reviewers and approvers and the status.
8016//
8017/**
8018 * Class to represent a list of document contents
8019 *
8020 * @category   DMS
8021 * @package    SeedDMS_Core
8022 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
8023 *             Uwe Steinmann <uwe@steinmann.cx>
8024 * @copyright  Copyright (C) 2002-2005 Markus Westphal,
8025 *             2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli,
8026 *             2010-2024 Uwe Steinmann
8027 * @version    Release: @package_version@
8028 */
8029class SeedDMS_Core_AddContentResultSet { /* {{{ */
8030
8031    /**
8032     * @var null
8033     */
8034    protected $_indReviewers;
8035
8036    /**
8037     * @var null
8038     */
8039    protected $_grpReviewers;
8040
8041    /**
8042     * @var null
8043     */
8044    protected $_indApprovers;
8045
8046    /**
8047     * @var null
8048     */
8049    protected $_grpApprovers;
8050
8051    /**
8052     * @var
8053     */
8054    protected $_content;
8055
8056    /**
8057     * @var null
8058     */
8059    protected $_status;
8060
8061    /**
8062     * @var SeedDMS_Core_DMS back reference to document management system
8063     */
8064    protected $_dms;
8065
8066    /**
8067     * SeedDMS_Core_AddContentResultSet constructor.
8068     * @param $content
8069     */
8070    public function __construct($content) { /* {{{ */
8071        $this->_content = $content;
8072        $this->_indReviewers = null;
8073        $this->_grpReviewers = null;
8074        $this->_indApprovers = null;
8075        $this->_grpApprovers = null;
8076        $this->_status = null;
8077        $this->_dms = null;
8078    } /* }}} */
8079
8080    /**
8081     * Set dms this object belongs to.
8082     *
8083     * Each object needs a reference to the dms it belongs to. It will be
8084     * set when the object is created.
8085     * The dms has a references to the currently logged in user
8086     * and the database connection.
8087     *
8088     * @param SeedDMS_Core_DMS $dms reference to dms
8089     */
8090    public function setDMS($dms) { /* {{{ */
8091        $this->_dms = $dms;
8092    } /* }}} */
8093
8094    /**
8095     * @param $reviewer
8096     * @param $type
8097     * @param $status
8098     * @return bool
8099     */
8100    public function addReviewer($reviewer, $type, $status) { /* {{{ */
8101        $dms = $this->_dms;
8102
8103        if (!is_object($reviewer) || (strcasecmp($type, "i") && strcasecmp($type, "g")) && !is_integer($status)){
8104            return false;
8105        }
8106        if (!strcasecmp($type, "i")) {
8107            if (strcasecmp(get_class($reviewer), $dms->getClassname("user"))) {
8108                return false;
8109            }
8110            if ($this->_indReviewers == null) {
8111                $this->_indReviewers = array();
8112            }
8113            $this->_indReviewers[$status][] = $reviewer;
8114        }
8115        if (!strcasecmp($type, "g")) {
8116            if (strcasecmp(get_class($reviewer), $dms->getClassname("group"))) {
8117                return false;
8118            }
8119            if ($this->_grpReviewers == null) {
8120                $this->_grpReviewers = array();
8121            }
8122            $this->_grpReviewers[$status][] = $reviewer;
8123        }
8124        return true;
8125    } /* }}} */
8126
8127    /**
8128     * @param $approver
8129     * @param $type
8130     * @param $status
8131     * @return bool
8132     */
8133    public function addApprover($approver, $type, $status) { /* {{{ */
8134        $dms = $this->_dms;
8135
8136        if (!is_object($approver) || (strcasecmp($type, "i") && strcasecmp($type, "g")) && !is_integer($status)){
8137            return false;
8138        }
8139        if (!strcasecmp($type, "i")) {
8140            if (strcasecmp(get_class($approver), $dms->getClassname("user"))) {
8141                return false;
8142            }
8143            if ($this->_indApprovers == null) {
8144                $this->_indApprovers = array();
8145            }
8146            $this->_indApprovers[$status][] = $approver;
8147        }
8148        if (!strcasecmp($type, "g")) {
8149            if (strcasecmp(get_class($approver), $dms->getClassname("group"))) {
8150                return false;
8151            }
8152            if ($this->_grpApprovers == null) {
8153                $this->_grpApprovers = array();
8154            }
8155            $this->_grpApprovers[$status][] = $approver;
8156        }
8157        return true;
8158    } /* }}} */
8159
8160    /**
8161     * @param $status
8162     * @return bool
8163     */
8164    public function setStatus($status) { /* {{{ */
8165        if (!is_integer($status)) {
8166            return false;
8167        }
8168        if ($status<-3 || $status>3) {
8169            return false;
8170        }
8171        $this->_status = $status;
8172        return true;
8173    } /* }}} */
8174
8175    /**
8176     * @return null
8177     */
8178    public function getStatus() { /* {{{ */
8179        return $this->_status;
8180    } /* }}} */
8181
8182    /**
8183     * @return mixed
8184     */
8185    public function getContent() { /* {{{ */
8186        return $this->_content;
8187    } /* }}} */
8188
8189    /**
8190     * @param $type
8191     * @return array|bool|null
8192     */
8193    public function getReviewers($type) { /* {{{ */
8194        if (strcasecmp($type, "i") && strcasecmp($type, "g")) {
8195            return false;
8196        }
8197        if (!strcasecmp($type, "i")) {
8198            return ($this->_indReviewers == null ? array() : $this->_indReviewers);
8199        }
8200        else {
8201            return ($this->_grpReviewers == null ? array() : $this->_grpReviewers);
8202        }
8203    } /* }}} */
8204
8205    /**
8206     * @param $type
8207     * @return array|bool|null
8208     */
8209    public function getApprovers($type) { /* {{{ */
8210        if (strcasecmp($type, "i") && strcasecmp($type, "g")) {
8211            return false;
8212        }
8213        if (!strcasecmp($type, "i")) {
8214            return ($this->_indApprovers == null ? array() : $this->_indApprovers);
8215        }
8216        else {
8217            return ($this->_grpApprovers == null ? array() : $this->_grpApprovers);
8218        }
8219    } /* }}} */
8220} /* }}} */