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