Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
67.18% covered (warning)
67.18%
1785 / 2657
40.82% covered (danger)
40.82%
80 / 196
CRAP
0.00% covered (danger)
0.00%
0 / 5
SeedDMS_Core_Document
71.67% covered (warning)
71.67%
931 / 1299
43.21% covered (danger)
43.21%
35 / 81
8037.92
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
4
 clearCache
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 __toString
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 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
65.22% covered (warning)
65.22%
15 / 23
0.00% covered (danger)
0.00%
0 / 1
14.21
 getDefaultAccess
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 setDefaultAccess
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 inheritsAccess
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getInheritAccess
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setInheritAccess
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 expires
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getExpires
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setExpires
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 hasExpired
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 verifyLastestContentExpriry
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
9.89
 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
 getSequence
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setSequence
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 clearAccessList
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
 getAccessList
95.45% covered (success)
95.45%
21 / 22
0.00% covered (danger)
0.00%
0 / 1
12
 addAccess
88.89% covered (warning)
88.89%
16 / 18
0.00% covered (danger)
0.00%
0 / 1
8.09
 changeAccess
87.50% covered (warning)
87.50%
14 / 16
0.00% covered (danger)
0.00%
0 / 1
5.05
 removeAccess
81.25% covered (warning)
81.25%
13 / 16
0.00% covered (danger)
0.00%
0 / 1
6.24
 getAccessMode
72.50% covered (warning)
72.50%
29 / 40
0.00% covered (danger)
0.00%
0 / 1
40.06
 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.04% covered (warning)
51.04%
49 / 96
0.00% covered (danger)
0.00%
0 / 1
197.65
 replaceContent
72.55% covered (warning)
72.55%
37 / 51
0.00% covered (danger)
0.00%
0 / 1
24.70
 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
38.30% covered (danger)
38.30%
36 / 94
0.00% covered (danger)
0.00%
0 / 1
322.76
 removeContent
62.96% covered (warning)
62.96%
17 / 27
0.00% covered (danger)
0.00%
0 / 1
23.96
 getDocumentLink
82.35% covered (warning)
82.35%
14 / 17
0.00% covered (danger)
0.00%
0 / 1
8.35
 getDocumentLinks
95.45% covered (success)
95.45%
21 / 22
0.00% covered (danger)
0.00%
0 / 1
10
 getReverseDocumentLinks
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
8.01
 addDocumentLink
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
10.01
 removeDocumentLink
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
 getDocumentFile
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
6.02
 getDocumentFiles
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
9
 addDocumentFile
68.00% covered (warning)
68.00%
17 / 25
0.00% covered (danger)
0.00%
0 / 1
11.65
 removeDocumentFile
66.67% covered (warning)
66.67%
14 / 21
0.00% covered (danger)
0.00%
0 / 1
13.70
 remove
58.21% covered (warning)
58.21%
39 / 67
0.00% covered (danger)
0.00%
0 / 1
80.21
 __getApproversList
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getReadAccessList
69.09% covered (warning)
69.09%
38 / 55
0.00% covered (danger)
0.00%
0 / 1
45.96
 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
92.00% covered (success)
92.00%
23 / 25
0.00% covered (danger)
0.00%
0 / 1
8.03
 transferToUser
62.50% covered (warning)
62.50%
15 / 24
0.00% covered (danger)
0.00%
0 / 1
7.90
SeedDMS_Core_DocumentContent
63.36% covered (warning)
63.36%
742 / 1171
20.83% covered (danger)
20.83%
15 / 72
14288.81
0.00% covered (danger)
0.00%
0 / 1
 verifyStatus
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
20
 __construct
95.24% covered (success)
95.24%
20 / 21
0.00% covered (danger)
0.00%
0 / 1
3
 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
 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
 exists
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 size
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 content
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 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
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
4.32
 getChecksum
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRealChecksum
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setChecksum
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
4.32
 getRealMimeType
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setFileType
77.78% covered (warning)
77.78%
14 / 18
0.00% covered (danger)
0.00%
0 / 1
6.40
 setMimeType
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
4.32
 setComment
47.06% covered (danger)
47.06%
8 / 17
0.00% covered (danger)
0.00%
0 / 1
17.50
 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
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
198.00
 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
 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
 delIndReviewer
70.59% covered (warning)
70.59%
12 / 17
0.00% covered (danger)
0.00%
0 / 1
9.63
 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
 setWorkflowState
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
3.14
 getWorkflowState
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
5.01
 setWorkflow
72.22% covered (warning)
72.22%
13 / 18
0.00% covered (danger)
0.00%
0 / 1
6.77
 getWorkflow
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
5.01
 rewriteWorkflowLog
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
30
 rewindWorkflow
75.00% covered (warning)
75.00%
9 / 12
0.00% covered (danger)
0.00%
0 / 1
3.14
 removeWorkflow
86.21% covered (warning)
86.21%
25 / 29
0.00% covered (danger)
0.00%
0 / 1
8.17
 getParentWorkflow
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
42
 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 / 29
0.00% covered (danger)
0.00%
0 / 1
90
 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
65.00% covered (warning)
65.00%
13 / 20
0.00% covered (danger)
0.00%
0 / 1
12.47
 enterNextState
76.00% covered (warning)
76.00%
19 / 25
0.00% covered (danger)
0.00%
0 / 1
12.67
 getWorkflowLog
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
5.01
 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%
10 / 10
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
71.58% covered (warning)
71.58%
68 / 95
76.92% covered (warning)
76.92%
20 / 26
94.58
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
13 / 13
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
 getRealMimeType
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 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
 exists
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 size
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 content
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setVersion
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 isPublic
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setPublic
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 getAccessMode
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
6.00
SeedDMS_Core_AddContentResultSet
22.41% covered (danger)
22.41%
13 / 58
33.33% covered (danger)
33.33%
3 / 9
865.86
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 setDMS
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addReviewer
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
132
 addApprover
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
132
 setStatus
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
4.59
 getStatus
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getContent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getReviewers
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
42
 getApprovers
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2declare(strict_types=1);
3
4/**
5 * Implementation of a document in the document management system
6 *
7 * @category   DMS
8 * @package    SeedDMS_Core
9 * @license    GPL2
10 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
11 *             Uwe Steinmann <uwe@steinmann.cx>
12 * @copyright  Copyright (C) 2002-2005 Markus Westphal, 2006-2008 Malcolm Cowe,
13 *             2010 Matteo Lucarelli, 2010-2024 Uwe Steinmann
14 * @version    Release: @package_version@
15 */
16
17/**
18 * The different states a document can be in
19 */
20/*
21 * Document is in review state. A document is in review state when
22 * it needs to be reviewed by a user or group.
23 */
24define("S_DRAFT_REV", 0);
25
26/*
27 * Document is in approval state. A document is in approval state when
28 * it needs to be approved by a user or group.
29 */
30define("S_DRAFT_APP", 1);
31
32/*
33 * Document is released. A document is in release state either when
34 * it needs no review or approval after uploaded or has been reviewed
35 * and/or approved.
36 */
37define("S_RELEASED", 2);
38
39/*
40 * Document is in workflow. A document is in workflow if a workflow
41 * has been started and has not reached a final state.
42 */
43define("S_IN_WORKFLOW", 3);
44
45/*
46 * Document was rejected. A document is in rejected state when
47 * the review failed or approval was not given.
48 */
49define("S_REJECTED", -1);
50
51/*
52 * Document is obsolete. A document can be obsoleted once it was
53 * released.
54 */
55define("S_OBSOLETE", -2);
56
57/*
58 * Document is expired. A document expires when the expiration date
59 * is reached
60 */
61define("S_EXPIRED", -3);
62
63/*
64 * Lowest and highest status that may be set
65 */
66define("S_LOWEST_STATUS", -3);
67define("S_HIGHEST_STATUS", 3);
68
69/**
70 * The different states a workflow log can be in. This is used in
71 * all tables tblDocumentXXXLog
72 */
73/*
74 * workflow is in a neutral status waiting for action of user
75 */
76define("S_LOG_WAITING", 0);
77
78/*
79 * workflow has been successful ended. The document content has been
80 * approved, reviewed, aknowledged or revised
81 */
82define("S_LOG_ACCEPTED", 1);
83
84/*
85 * workflow has been unsuccessful ended. The document content has been
86 * rejected
87 */
88define("S_LOG_REJECTED", -1);
89
90/*
91 * user has been removed from workflow. This can be for different reasons
92 * 1. the user has been actively removed from the workflow, 2. the user has
93 * been deleted.
94 */
95define("S_LOG_USER_REMOVED", -2);
96
97/*
98 * workflow is sleeping until reactivation. The workflow has been set up
99 * but not started. This is only valid for the revision workflow, which
100 * may run over and over again.
101 */
102define("S_LOG_SLEEPING", -3);
103
104/**
105 * Class to represent a document in the document management system
106 *
107 * A document in SeedDMS is a collection of content elements which are
108 * similar to a file in a regular file system.
109 * Documents may have any number of content elements
110 * ({@see SeedDMS_Core_DocumentContent}). These content elements are often
111 * called versions ordered in a timely manner. The most recent content element
112 * is the current version of the document.
113 *
114 * Documents can be linked to other documents, can have attached files,
115 * can be assigned to a category and have additional attributes.
116 * The document content can be anything that can be stored in a regular
117 * file.
118 *
119 * @category   DMS
120 * @package    SeedDMS_Core
121 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
122 *             Uwe Steinmann <uwe@steinmann.cx>
123 * @copyright  Copyright (C) 2002-2005 Markus Westphal, 2006-2008 Malcolm Cowe,
124 *             2010 Matteo Lucarelli, 2010-2024 Uwe Steinmann
125 * @version    Release: @package_version@
126 */
127class SeedDMS_Core_Document extends SeedDMS_Core_Object { /* {{{ */
128    /**
129     * @var string name of document
130     */
131    protected $_name;
132
133    /**
134     * @var string comment of document
135     */
136    protected $_comment;
137
138    /**
139     * @var integer unix timestamp of creation date
140     */
141    protected $_date;
142
143    /**
144     * @var integer id of user who is the owner
145     */
146    protected $_ownerID;
147
148    /**
149     * @var object user who is the owner
150     */
151    protected $_owner;
152
153    /**
154     * @var integer id of folder this document belongs to
155     */
156    protected $_folderID;
157
158    /**
159     * @var object parent folder this document belongs to
160     */
161    protected $_parent;
162
163    /**
164     * @var integer timestamp of expiration date
165     */
166    protected $_expires;
167
168    /**
169     * @var boolean true if access is inherited, otherwise false
170     */
171    protected $_inheritAccess;
172
173    /**
174     * @var integer default access if access rights are not inherited
175     */
176    protected $_defaultAccess;
177
178    /**
179     * @var array list of notifications for users and groups
180     */
181    protected $_readAccessList;
182
183    /**
184     * @var array list of notifications for users and groups
185     */
186    public $_notifyList;
187
188    /**
189     * @var boolean true if document is locked, otherwise false
190     */
191    protected $_locked;
192
193    /**
194     * @var object user who has locked the document
195     */
196    protected $_lockingUser;
197
198    /**
199     * @var string list of keywords
200     */
201    protected $_keywords;
202
203    /**
204     * @var SeedDMS_Core_DocumentCategory[] list of categories
205     */
206    protected $_categories;
207
208    /**
209     * @var integer position of document within the parent folder
210     */
211    protected $_sequence;
212
213    /**
214     * @var SeedDMS_Core_DocumentContent temp. storage for latestcontent
215     */
216    protected $_latestContent;
217
218    /**
219     * @var array temp. storage for content
220     */
221    protected $_content;
222
223    /**
224     * @var SeedDMS_Core_Folder
225     */
226    protected $_folder;
227
228    /** @var array of SeedDMS_Core_UserAccess and SeedDMS_Core_GroupAccess */
229    protected $_accessList;
230
231    /**
232     * @var array
233     */
234    protected $_documentLinks;
235
236    /**
237     * @var array
238     */
239    protected $_documentFiles;
240
241    public function __construct($id, $name, $comment, $date, $expires, $ownerID, $folderID, $inheritAccess, $defaultAccess, $locked, $keywords, $sequence) { /* {{{ */
242        parent::__construct($id);
243        $this->_name = trim($name);
244        $this->_comment = trim($comment);
245        $this->_date = $date;
246        $this->_expires = $expires;
247        $this->_ownerID = $ownerID;
248        $this->_folderID = $folderID;
249        $this->_inheritAccess = $inheritAccess ? true : false;
250        $this->_defaultAccess = $defaultAccess;
251        $this->_locked = ($locked == null || $locked == '' ? -1 : $locked);
252        $this->_lockingUser = null;
253        $this->_keywords = trim($keywords);
254        $this->_sequence = $sequence;
255        $this->_categories = array();
256        $this->_notifyList = array();
257        $this->_latestContent = null;
258        $this->_content = null;
259        /* Cache */
260        $this->clearCache();
261    } /* }}} */
262
263    /**
264     * Clear cache of this instance.
265     *
266     * The result of some expensive database actions (e.g. get all subfolders
267     * or documents) will be saved in a class variable to speed up consecutive
268     * calls of the same method. If a second call of the same method shall not
269     * use the cache, then it must be cleared.
270     *
271     */
272    public function clearCache() { /* {{{ */
273        $this->_parent = null;
274        $this->_owner = null;
275        $this->_documentLinks = null;
276        $this->_documentFiles = null;
277        $this->_content = null;
278        $this->_accessList = null;
279        $this->_notifyList = array();
280        $this->_readAccessList = array();
281    } /* }}} */
282
283    /**
284     * Cast to string
285     *
286     * @return string
287     */
288    public function __toString() { /* {{{ */
289        return $this->_name;
290    } /* }}} */
291
292    /**
293     * Check if this object is of type 'document'.
294     *
295     * @param string $type type of object
296     */
297    public function isType($type) { /* {{{ */
298        return $type == 'document';
299    } /* }}} */
300
301    /**
302     * Return an array of database fields which are used for searching
303     * a term entered in the database search form
304     *
305     * @param SeedDMS_Core_DMS $dms
306     * @param array $searchin integer list of search scopes (2=name, 3=comment,
307     * 4=attributes)
308     * @return array list of database fields
309     */
310    public static function getSearchFields($dms, $searchin) { /* {{{ */
311        $db = $dms->getDB();
312
313        $searchFields = array();
314        if (in_array(1, $searchin)) {
315            $searchFields[] = "`tblDocuments`.`keywords`";
316        }
317        if (in_array(2, $searchin)) {
318            $searchFields[] = "`tblDocuments`.`name`";
319        }
320        if (in_array(3, $searchin)) {
321            $searchFields[] = "`tblDocuments`.`comment`";
322            $searchFields[] = "`tblDocumentContent`.`comment`";
323        }
324        if (in_array(4, $searchin)) {
325            $searchFields[] = "`tblDocumentAttributes`.`value`";
326            $searchFields[] = "`tblDocumentContentAttributes`.`value`";
327        }
328        if (in_array(5, $searchin)) {
329            $searchFields[] = $db->castToText("`tblDocuments`.`id`");
330        }
331
332        return $searchFields;
333    } /* }}} */
334
335    /**
336     * Return a folder by its database record
337     *
338     * @param array $resArr array of folder data as returned by database
339     * @param SeedDMS_Core_DMS $dms
340     * @return SeedDMS_Core_Folder|bool instance of SeedDMS_Core_Folder if document exists
341     */
342    public static function getInstanceByData($resArr, $dms) { /* {{{ */
343        $classname = $dms->getClassname('document');
344        /** @var SeedDMS_Core_Document $document */
345        $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"]);
346        $document->setDMS($dms);
347        $document = $document->applyDecorators();
348        return $document;
349    } /* }}} */
350
351    /**
352     * Return an document by its id
353     *
354     * @param integer $id id of document
355     * @param SeedDMS_Core_DMS $dms
356     * @return bool|SeedDMS_Core_Document instance of SeedDMS_Core_Document if document exists, null
357     * if document does not exist, false in case of error
358     */
359    public static function getInstance($id, $dms) { /* {{{ */
360        $db = $dms->getDB();
361
362//        $queryStr = "SELECT * FROM `tblDocuments` WHERE `id` = " . (int) $id;
363        $queryStr = "SELECT `tblDocuments`.*, `tblDocumentLocks`.`userID` as `lock` FROM `tblDocuments` LEFT JOIN `tblDocumentLocks` ON `tblDocuments`.`id` = `tblDocumentLocks`.`document` WHERE `id` = " . (int) $id;
364        if ($dms->checkWithinRootDir)
365            $queryStr .= " AND `folderList` LIKE '%:".$dms->rootFolderID.":%'";
366        $resArr = $db->getResultArray($queryStr);
367        if (is_bool($resArr) && $resArr == false)
368            return false;
369        if (count($resArr) != 1)
370            return null;
371        $resArr = $resArr[0];
372
373        $resArr['lock'] = !$resArr['lock'] ? -1 : $resArr['lock'];
374
375        return self::getInstanceByData($resArr, $dms);
376    } /* }}} */
377
378    /**
379     * Apply decorators
380     *
381     * @return object final object after all decorators has been applied
382     */
383    public function applyDecorators() { /* {{{ */
384        if ($decorators = $this->_dms->getDecorators('document')) {
385            $s = $this;
386            foreach ($decorators as $decorator) {
387                $s = new $decorator($s);
388            }
389            return $s;
390        } else {
391            return $this;
392        }
393    } /* }}} */
394
395    /**
396     * Return the directory of the document in the file system relativ
397     * to the contentDir
398     *
399     * @return string directory of document
400     */
401    public function getDir() { /* {{{ */
402        if ($this->_dms->maxDirID) {
403            $dirid = (int) (($this->_id-1) / $this->_dms->maxDirID) + 1;
404            return $dirid.DIRECTORY_SEPARATOR.$this->_id.DIRECTORY_SEPARATOR;
405        } else {
406            return $this->_id.DIRECTORY_SEPARATOR;
407        }
408    } /* }}} */
409
410    /**
411     * Return the name of the document
412     *
413     * @return string name of document
414     */
415    public function getName() { return $this->_name; }
416
417    /**
418     * Set the name of the document
419     *
420     * @param $newName string new name of document
421     * @return bool
422     */
423    public function setName($newName) { /* {{{ */
424        $db = $this->_dms->getDB();
425
426        /* Check if 'onPreSetName' callback is set */
427        if (isset($this->_dms->callbacks['onPreSetName'])) {
428            foreach ($this->_dms->callbacks['onPreSetName'] as $callback) {
429                $ret = call_user_func($callback[0], $callback[1], $this, $newName);
430                if (is_bool($ret))
431                    return $ret;
432            }
433        }
434
435        $queryStr = "UPDATE `tblDocuments` SET `name` = ".$db->qstr($newName)." WHERE `id` = ". $this->_id;
436        if (!$db->getResult($queryStr))
437            return false;
438
439        $oldName = $this->_name;
440        $this->_name = $newName;
441
442        /* Check if 'onPostSetName' callback is set */
443        if (isset($this->_dms->callbacks['onPostSetName'])) {
444            foreach ($this->_dms->callbacks['onPostSetName'] as $callback) {
445                $ret = call_user_func($callback[0], $callback[1], $this, $oldName);
446                if (is_bool($ret))
447                    return $ret;
448            }
449        }
450
451        return true;
452    } /* }}} */
453
454    /**
455     * Return the comment of the document
456     *
457     * @return string comment of document
458     */
459    public function getComment() { return $this->_comment; }
460
461    /**
462     * Set the comment of the document
463     *
464     * @param $newComment string new comment of document
465     * @return bool
466     */
467    public function setComment($newComment) { /* {{{ */
468        $db = $this->_dms->getDB();
469
470        /* Check if 'onPreSetComment' callback is set */
471        if (isset($this->_dms->callbacks['onPreSetComment'])) {
472            foreach ($this->_dms->callbacks['onPreSetComment'] as $callback) {
473                $ret = call_user_func($callback[0], $callback[1], $this, $newComment);
474                if (is_bool($ret))
475                    return $ret;
476            }
477        }
478
479        $queryStr = "UPDATE `tblDocuments` SET `comment` = ".$db->qstr($newComment)." WHERE `id` = ". $this->_id;
480        if (!$db->getResult($queryStr))
481            return false;
482
483        $oldComment = $this->_comment;
484        $this->_comment = $newComment;
485
486        /* Check if 'onPostSetComment' callback is set */
487        if (isset($this->_dms->callbacks['onPostSetComment'])) {
488            foreach ($this->_dms->callbacks['onPostSetComment'] as $callback) {
489                $ret = call_user_func($callback[0], $callback[1], $this, $oldComment);
490                if (is_bool($ret))
491                    return $ret;
492            }
493        }
494
495        return true;
496    } /* }}} */
497
498    /**
499     * @return string
500     */
501    public function getKeywords() { return $this->_keywords; }
502
503    /**
504     * @param string $newKeywords
505     * @return bool
506     */
507    public function setKeywords($newKeywords) { /* {{{ */
508        $db = $this->_dms->getDB();
509
510        /* Check if 'onPreSetKeywords' callback is set */
511        if (isset($this->_dms->callbacks['onPreSetKeywords'])) {
512            foreach ($this->_dms->callbacks['onPreSetKeywords'] as $callback) {
513                $ret = call_user_func($callback[0], $callback[1], $this, $newKeywords);
514                if (is_bool($ret))
515                    return $ret;
516            }
517        }
518
519        $queryStr = "UPDATE `tblDocuments` SET `keywords` = ".$db->qstr($newKeywords)." WHERE `id` = ". $this->_id;
520        if (!$db->getResult($queryStr))
521            return false;
522
523        $oldKeywords = $this->_keywords;
524        $this->_keywords = $newKeywords;
525
526        /* Check if 'onPostSetKeywords' callback is set */
527        if (isset($this->_dms->callbacks['onPostSetKeywords'])) {
528            foreach ($this->_dms->callbacks['onPostSetKeywords'] as $callback) {
529                $ret = call_user_func($callback[0], $callback[1], $this, $oldKeywords);
530                if (is_bool($ret))
531                    return $ret;
532            }
533        }
534
535        return true;
536    } /* }}} */
537
538    /**
539     * Check if document has a given category
540     *
541     * @param SeedDMS_Core_DocumentCategory $cat
542     * @return bool true if document has category, otherwise false
543     */
544    public function hasCategory($cat) { /* {{{ */
545        $db = $this->_dms->getDB();
546
547        if (!$cat)
548            return false;
549
550        $queryStr = "SELECT * FROM `tblDocumentCategory` WHERE `documentID` = ".$this->_id." AND `categoryID`=".$cat->getId();
551        $resArr = $db->getResultArray($queryStr);
552        if (!$resArr)
553            return false;
554
555        return true;
556    } /* }}} */
557
558    /**
559     * Retrieve a list of all categories this document belongs to
560     *
561     * @return bool|SeedDMS_Core_DocumentCategory[]
562     */
563    public function getCategories() { /* {{{ */
564        $db = $this->_dms->getDB();
565
566        if (!$this->_categories) {
567            $queryStr = "SELECT * FROM `tblCategory` WHERE `id` IN (SELECT `categoryID` FROM `tblDocumentCategory` WHERE `documentID` = ".$this->_id.")";
568            $resArr = $db->getResultArray($queryStr);
569            if (is_bool($resArr) && !$resArr)
570                return false;
571
572            $this->_categories = [];
573            foreach ($resArr as $row) {
574                $cat = new SeedDMS_Core_DocumentCategory($row['id'], $row['name']);
575                $cat->setDMS($this->_dms);
576                $this->_categories[] = $cat;
577            }
578        }
579        return $this->_categories;
580    } /* }}} */
581
582    /**
583     * Set a list of categories for the document
584     *
585     * This method will delete currently assigned categories and sets new
586     * categories.
587     *
588     * @param SeedDMS_Core_DocumentCategory[] $newCategories list of category objects
589     * @return bool
590     */
591    public function setCategories($newCategories) { /* {{{ */
592        $db = $this->_dms->getDB();
593
594        /* Check if 'onPreSetCategories' callback is set */
595        if (isset($this->_dms->callbacks['onPreSetCategories'])) {
596            foreach ($this->_dms->callbacks['onPreSetCategories'] as $callback) {
597                $ret = call_user_func($callback[0], $callback[1], $this, $newCategories);
598                if (is_bool($ret))
599                    return $ret;
600            }
601        }
602
603        $db->startTransaction();
604        $queryStr = "DELETE FROM `tblDocumentCategory` WHERE `documentID` = ". $this->_id;
605        if (!$db->getResult($queryStr)) {
606            $db->rollbackTransaction();
607            return false;
608        }
609
610        foreach ($newCategories as $cat) {
611            $queryStr = "INSERT INTO `tblDocumentCategory` (`categoryID`, `documentID`) VALUES (". $cat->getId() .", ". $this->_id .")";
612            if (!$db->getResult($queryStr)) {
613                $db->rollbackTransaction();
614                return false;
615            }
616        }
617
618        $db->commitTransaction();
619
620        $oldCategories = $this->_categories;
621        $this->_categories = $newCategories;
622
623        /* Check if 'onPostSetCategories' callback is set */
624        if (isset($this->_dms->callbacks['onPostSetCategories'])) {
625            foreach ($this->_dms->callbacks['onPostSetCategories'] as $callback) {
626                $ret = call_user_func($callback[0], $callback[1], $this, $oldCategories);
627                if (is_bool($ret))
628                    return $ret;
629            }
630        }
631
632        return true;
633    } /* }}} */
634
635    /**
636     * Add a list of categories to the document
637     *
638     * This method will add a list of new categories to the document.
639     *
640     * @param array $newCategories list of category objects
641     */
642    public function addCategories($newCategories) { /* {{{ */
643        $db = $this->_dms->getDB();
644
645        /* Check if 'onPreAddCategories' callback is set */
646        if (isset($this->_dms->callbacks['onPreAddCategories'])) {
647            foreach ($this->_dms->callbacks['onPreAddCategories'] as $callback) {
648                $ret = call_user_func($callback[0], $callback[1], $this, $newCategories);
649                if (is_bool($ret))
650                    return $ret;
651            }
652        }
653
654        if (!$this->_categories)
655            $this->getCategories();
656
657        $catids = array();
658        foreach ($this->_categories as $cat)
659            $catids[] = $cat->getID();
660
661        $db->startTransaction();
662        $ncat = array(); // Array containing actually added new categories
663        foreach ($newCategories as $cat) {
664            if (!in_array($cat->getID(), $catids)) {
665                $queryStr = "INSERT INTO `tblDocumentCategory` (`categoryID`, `documentID`) VALUES (". $cat->getId() .", ". $this->_id .")";
666                if (!$db->getResult($queryStr)) {
667                    $db->rollbackTransaction();
668                    return false;
669                }
670                $ncat[] = $cat;
671            }
672        }
673        $db->commitTransaction();
674
675        $oldCategories = $this->_categories;
676        $this->_categories = array_merge($this->_categories, $ncat);
677
678        /* Check if 'onPostAddCategories' callback is set */
679        if (isset($this->_dms->callbacks['onPostAddCategories'])) {
680            foreach ($this->_dms->callbacks['onPostAddCategories'] as $callback) {
681                $ret = call_user_func($callback[0], $callback[1], $this, $oldCategories);
682                if (is_bool($ret))
683                    return $ret;
684            }
685        }
686
687        return true;
688    } /* }}} */
689
690    /**
691     * Remove a list of categories from the document
692     *
693     * This method will remove a list of assigned categories to the document.
694     *
695     * @param array $newCategories list of category objects
696     */
697    public 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    public 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    public 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    public 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     * @see SeedDMS_Core_Document::getFolder()
788     *
789     * @return SeedDMS_Core_Folder parent folder
790     */
791    public function getParent() { /* {{{ */
792        return $this->getFolder();
793    } /* }}} */
794
795    /**
796     * Return the parent folder of the document
797     *
798     * @return SeedDMS_Core_Folder parent folder
799     */
800    public function getFolder() { /* {{{ */
801        if (!isset($this->_folder))
802            $this->_folder = $this->_dms->getFolder($this->_folderID);
803        return $this->_folder;
804    } /* }}} */
805
806    /**
807     * Set folder of a document
808     *
809     * This method basically moves a document from a folder to another
810     * folder.
811     *
812     * @param SeedDMS_Core_Folder $newFolder
813     * @return boolean false in case of an error, otherwise true
814     */
815    public function setParent($newFolder) { /* {{{ */
816        return $this->setFolder($newFolder);
817    } /* }}} */
818
819    /**
820     * Set folder of a document
821     *
822     * This method basically moves a document from a folder to another
823     * folder.
824     *
825     * @param SeedDMS_Core_Folder $newFolder
826     * @return boolean false in case of an error, otherwise true
827     */
828    public function setFolder($newFolder) { /* {{{ */
829        $db = $this->_dms->getDB();
830
831        if (!$newFolder)
832            return false;
833
834        if (!$newFolder->isType('folder'))
835            return false;
836
837        /* Check if 'onPreSetFolder' callback is set */
838        if (isset($this->_dms->callbacks['onPreSetFolder'])) {
839            foreach ($this->_dms->callbacks['onPreSetFolder'] as $callback) {
840                $ret = call_user_func($callback[0], $callback[1], $this, $newFolder);
841                if (is_bool($ret))
842                    return $ret;
843            }
844        }
845
846        $db->startTransaction();
847
848        $queryStr = "UPDATE `tblDocuments` SET `folder` = " . $newFolder->getID() . " WHERE `id` = ". $this->_id;
849        if (!$db->getResult($queryStr)) {
850            $db->rollbackTransaction();
851            return false;
852        }
853
854        // Make sure that the folder search path is also updated.
855        $path = $newFolder->getPath();
856        $flist = "";
857        /** @var SeedDMS_Core_Folder[] $path */
858        foreach ($path as $f) {
859            $flist .= ":".$f->getID();
860        }
861        if (strlen($flist)>1) {
862            $flist .= ":";
863        }
864        $queryStr = "UPDATE `tblDocuments` SET `folderList` = '" . $flist . "' WHERE `id` = ". $this->_id;
865        if (!$db->getResult($queryStr)) {
866            $db->rollbackTransaction();
867            return false;
868        }
869
870        $db->commitTransaction();
871
872        $oldFolder = $this->_folder;
873        $this->_folderID = $newFolder->getID();
874        $this->_folder = $newFolder;
875
876        /* Check if 'onPostSetFolder' callback is set */
877        if (isset($this->_dms->callbacks['onPostSetFolder'])) {
878            foreach ($this->_dms->callbacks['onPostSetFolder'] as $callback) {
879                $ret = call_user_func($callback[0], $callback[1], $this, $oldFolder);
880                if (is_bool($ret))
881                    return $ret;
882            }
883        }
884
885        return true;
886    } /* }}} */
887
888    /**
889     * Return owner of document
890     *
891     * @return SeedDMS_Core_User owner of document as an instance of {@see SeedDMS_Core_User}
892     */
893    public function getOwner() { /* {{{ */
894        if (!isset($this->_owner))
895            $this->_owner = $this->_dms->getUser($this->_ownerID);
896        return $this->_owner;
897    } /* }}} */
898
899    /**
900     * Set owner of a document
901     *
902     * @param SeedDMS_Core_User $newOwner new owner
903     * @return boolean true if successful otherwise false
904     */
905    public function setOwner($newOwner) { /* {{{ */
906        $db = $this->_dms->getDB();
907
908        if (!$newOwner)
909            return false;
910
911        if (!$newOwner->isType('user'))
912            return false;
913
914        /* Check if 'onPreSetOwner' callback is set */
915        if (isset($this->_dms->callbacks['onPreSetOwner'])) {
916            foreach ($this->_dms->callbacks['onPreSetOwner'] as $callback) {
917                $ret = call_user_func($callback[0], $callback[1], $this, $newOwner);
918                if (is_bool($ret))
919                    return $ret;
920            }
921        }
922
923        $queryStr = "UPDATE `tblDocuments` set `owner` = " . $newOwner->getID() . " WHERE `id` = " . $this->_id;
924        if (!$db->getResult($queryStr))
925            return false;
926
927        $oldOwner = $this->_owner;
928        $this->_ownerID = $newOwner->getID();
929        $this->_owner = $newOwner;
930
931        $this->_readAccessList = array();
932
933        /* Check if 'onPostSetOwner' callback is set */
934        if (isset($this->_dms->callbacks['onPostSetOwner'])) {
935            foreach ($this->_dms->callbacks['onPostSetOwner'] as $callback) {
936                $ret = call_user_func($callback[0], $callback[1], $this, $oldOwner);
937                if (is_bool($ret))
938                    return $ret;
939            }
940        }
941
942        return true;
943    } /* }}} */
944
945    /**
946     * @return bool|int
947     */
948    public function getDefaultAccess() { /* {{{ */
949        if ($this->inheritsAccess()) {
950            $res = $this->getFolder();
951            if (!$res) return false;
952            return $this->_folder->getDefaultAccess();
953        }
954        return $this->_defaultAccess;
955    } /* }}} */
956
957    /**
958     * Set default access mode
959     *
960     * This method sets the default access mode and also removes all notifiers which
961     * will not have read access anymore. Setting a default access mode will only
962     * have an immediate effect if the access rights are not inherited, otherwise
963     * it just updates the database record of the document and once the
964     * inheritance is turn off the default access mode will take effect.
965     *
966     * @param integer     $mode    access mode
967     * @param bool|string $noclean set to true if notifier list shall not be clean up
968     *
969     * @return bool
970     */
971    public function setDefaultAccess($mode, $noclean = false) { /* {{{ */
972        $db = $this->_dms->getDB();
973
974        if ($mode < M_LOWEST_RIGHT || $mode > M_HIGHEST_RIGHT)
975            return false;
976
977        $queryStr = "UPDATE `tblDocuments` set `defaultAccess` = " . (int) $mode . " WHERE `id` = " . $this->_id;
978        if (!$db->getResult($queryStr))
979            return false;
980
981        $this->_defaultAccess = $mode;
982        $this->_readAccessList = array();
983
984        /* Setting the default access mode does not have any effect if access
985         * is still inherited. In that case there is no need to clean the
986         * notification list.
987         */
988        if (!$noclean && !$this->_inheritAccess)
989            $this->cleanNotifyList();
990
991        return true;
992    } /* }}} */
993
994    /**
995     * @return bool
996     */
997    public function inheritsAccess() { return $this->_inheritAccess; }
998
999    /**
1000     * This is supposed to be a replacement for inheritsAccess()
1001     *
1002     * @return bool
1003     */
1004    public function getInheritAccess() { return $this->_inheritAccess; }
1005
1006    /**
1007     * Set inherited access mode
1008     *
1009     * Setting inherited access mode will set or unset the internal flag which
1010     * controls if the access mode is inherited from the parent folder or not.
1011     * It will not modify the
1012     * access control list for the current object. It will remove all
1013     * notifications of users which do not even have read access anymore
1014     * after setting or unsetting inherited access.
1015     *
1016     * @param boolean $inheritAccess set to true for setting and false for
1017     *        unsetting inherited access mode
1018     * @param boolean $noclean set to true if notifier list shall not be clean up
1019     * @return boolean true if operation was successful otherwise false
1020     */
1021    public function setInheritAccess($inheritAccess, $noclean = false) { /* {{{ */
1022        $db = $this->_dms->getDB();
1023
1024        $queryStr = "UPDATE `tblDocuments` SET `inheritAccess` = " . ($inheritAccess ? "1" : "0") . " WHERE `id` = " . $this->_id;
1025        if (!$db->getResult($queryStr))
1026            return false;
1027
1028        $this->_inheritAccess = ($inheritAccess ? true : false);
1029        $this->_readAccessList = array();
1030
1031        if (!$noclean)
1032            $this->cleanNotifyList();
1033
1034        return true;
1035    } /* }}} */
1036
1037    /**
1038     * Check if document expires
1039     *
1040     * @return boolean true if document has expiration date set, otherwise false
1041     */
1042    public function expires() { /* {{{ */
1043        if (intval($this->_expires) == 0)
1044            return false;
1045        else
1046            return true;
1047    } /* }}} */
1048
1049    /**
1050     * Get expiration time of document
1051     *
1052     * @return integer/boolean expiration date as unix timestamp or false
1053     */
1054    public function getExpires() { /* {{{ */
1055        if (intval($this->_expires) == 0)
1056            return false;
1057        else
1058            return $this->_expires;
1059    } /* }}} */
1060
1061    /**
1062     * Set expiration date as unix timestamp
1063     *
1064     * @param integer $expires unix timestamp of expiration date
1065     * @return bool
1066     */
1067    public function setExpires($expires) { /* {{{ */
1068        $db = $this->_dms->getDB();
1069
1070        $expires = (!$expires) ? 0 : $expires;
1071
1072        if ($expires == $this->_expires) {
1073            // No change is necessary.
1074            return true;
1075        }
1076
1077        $queryStr = "UPDATE `tblDocuments` SET `expires` = " . (int) $expires . " WHERE `id` = " . $this->_id;
1078        if (!$db->getResult($queryStr))
1079            return false;
1080
1081        $this->_expires = $expires;
1082        return true;
1083    } /* }}} */
1084
1085    /**
1086     * Check if the document has expired
1087     *
1088     * The method expects to database field 'expired' to hold the timestamp
1089     * of the start of day at which end the document expires. The document will
1090     * expire if that day is over. Hence, a document will *not*
1091     * be expired during the day of expiration but at the end of that day
1092     *
1093     * @return boolean true if document has expired otherwise false
1094     */
1095    public function hasExpired() { /* {{{ */
1096        if (intval($this->_expires) == 0) return false;
1097        if (time()>=$this->_expires+24*60*60) return true;
1098        return false;
1099    } /* }}} */
1100
1101    /**
1102     * Check if the document has expired and set the status accordingly
1103     *
1104     * It will also recalculate the status if the current status is
1105     * set to S_EXPIRED but the document isn't actually expired.
1106     * The method will update the document status log database table
1107     * if needed.
1108     * FIXME: some left over reviewers/approvers are in the way if
1109     * no workflow is set and traditional workflow mode is on. In that
1110     * case the status is set to S_DRAFT_REV or S_DRAFT_APP
1111     *
1112     * @return boolean true if status has changed
1113     */
1114    public function verifyLastestContentExpriry() { /* {{{ */
1115        $lc = $this->getLatestContent();
1116        if ($lc) {
1117            $st = $lc->getStatus();
1118
1119            if (($st["status"]==S_DRAFT_REV || $st["status"]==S_DRAFT_APP || $st["status"]==S_IN_WORKFLOW || $st["status"]==S_RELEASED) && $this->hasExpired()) {
1120                return $lc->setStatus(S_EXPIRED, "", $this->getOwner());
1121            }
1122            elseif ($st["status"]==S_EXPIRED && !$this->hasExpired()) {
1123                $lc->verifyStatus(true, $this->getOwner());
1124                return true;
1125            }
1126        }
1127        return false;
1128    } /* }}} */
1129
1130    /**
1131     * Check if document is locked
1132     *
1133     * @return boolean true if locked otherwise false
1134     */
1135    public function isLocked() { return $this->_locked != -1; }
1136
1137    /**
1138     * Lock or unlock document
1139     *
1140     * @param SeedDMS_Core_User|bool $falseOrUser user object for locking or false for unlocking
1141     * @return boolean true if operation was successful otherwise false
1142     */
1143    public function setLocked($falseOrUser) { /* {{{ */
1144        $db = $this->_dms->getDB();
1145
1146        $lockUserID = -1;
1147        if (is_bool($falseOrUser) && !$falseOrUser) {
1148            $queryStr = "DELETE FROM `tblDocumentLocks` WHERE `document` = ".$this->_id;
1149        }
1150        elseif (is_object($falseOrUser)) {
1151            $queryStr = "INSERT INTO `tblDocumentLocks` (`document`, `userID`) VALUES (".$this->_id.", ".$falseOrUser->getID().")";
1152            $lockUserID = $falseOrUser->getID();
1153        }
1154        else {
1155            return false;
1156        }
1157        if (!$db->getResult($queryStr)) {
1158            return false;
1159        }
1160        unset($this->_lockingUser);
1161        $this->_locked = $lockUserID;
1162        return true;
1163    } /* }}} */
1164
1165    /**
1166     * Get the user currently locking the document
1167     *
1168     * @return SeedDMS_Core_User|bool user have a lock
1169     */
1170    public function getLockingUser() { /* {{{ */
1171        if (!$this->isLocked())
1172            return false;
1173
1174        if (!isset($this->_lockingUser))
1175            $this->_lockingUser = $this->_dms->getUser($this->_locked);
1176        return $this->_lockingUser;
1177    } /* }}} */
1178
1179    /**
1180     * @return float
1181     */
1182    public function getSequence() { return $this->_sequence; }
1183
1184    /**
1185     * @param float $seq
1186     * @return bool
1187     */
1188    public function setSequence($seq) { /* {{{ */
1189        $db = $this->_dms->getDB();
1190
1191        $queryStr = "UPDATE `tblDocuments` SET `sequence` = " . $seq . " WHERE `id` = " . $this->_id;
1192        if (!$db->getResult($queryStr))
1193            return false;
1194
1195        $this->_sequence = $seq;
1196        return true;
1197    } /* }}} */
1198
1199    /**
1200     * Delete all entries for this document from the access control list
1201     *
1202     * @param boolean $noclean set to true if notifier list shall not be clean up
1203     * @return boolean true if operation was successful otherwise false
1204     */
1205    public function clearAccessList($noclean = false) { /* {{{ */
1206        $db = $this->_dms->getDB();
1207
1208        $queryStr = "DELETE FROM `tblACLs` WHERE `targetType` = " . T_DOCUMENT . " AND `target` = " . $this->_id;
1209        if (!$db->getResult($queryStr))
1210            return false;
1211
1212        unset($this->_accessList);
1213        $this->_readAccessList = array();
1214
1215        if (!$noclean)
1216            $this->cleanNotifyList();
1217
1218        return true;
1219    } /* }}} */
1220
1221    /**
1222     * Returns a list of access privileges
1223     *
1224     * If the document inherits the access privileges from the parent folder
1225     * those will be returned.
1226     * $mode and $op can be set to restrict the list of returned access
1227     * privileges. If $mode is set to M_ANY no restriction will apply
1228     * regardless of the value of $op. The returned array contains a list
1229     * of {@see SeedDMS_Core_UserAccess} and
1230     * {@see SeedDMS_Core_GroupAccess} objects. Even if the document
1231     * has no access list the returned array contains the two elements
1232     * 'users' and 'groups' which are than empty. The methode returns false
1233     * if the function fails.
1234     *
1235     * @param int $mode access mode (defaults to M_ANY)
1236     * @param int|string $op operation (defaults to O_EQ)
1237     * @return bool|array
1238     */
1239    public function getAccessList($mode = M_ANY, $op = O_EQ) { /* {{{ */
1240        $db = $this->_dms->getDB();
1241
1242        if ($this->inheritsAccess()) {
1243            $res = $this->getFolder();
1244            if (!$res) return false;
1245            return $this->_folder->getAccessList($mode, $op);
1246        }
1247
1248        if (!isset($this->_accessList[$mode])) {
1249            if ($op!=O_GTEQ && $op!=O_LTEQ && $op!=O_EQ) {
1250                return false;
1251            }
1252            $modeStr = "";
1253            if ($mode!=M_ANY) {
1254                $modeStr = " AND `mode`".$op.(int)$mode;
1255            }
1256            $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT.
1257                " AND `target` = " . $this->_id . $modeStr . " ORDER BY `targetType`";
1258            $resArr = $db->getResultArray($queryStr);
1259            if (is_bool($resArr) && !$resArr)
1260                return false;
1261
1262            $this->_accessList[$mode] = array("groups" => array(), "users" => array());
1263            foreach ($resArr as $row) {
1264                if ($row["userID"] != -1)
1265                    array_push($this->_accessList[$mode]["users"], new SeedDMS_Core_UserAccess($this->_dms->getUser($row["userID"]), (int) $row["mode"]));
1266                else //if ($row["groupID"] != -1)
1267                    array_push($this->_accessList[$mode]["groups"], new SeedDMS_Core_GroupAccess($this->_dms->getGroup($row["groupID"]), (int) $row["mode"]));
1268            }
1269        }
1270
1271        return $this->_accessList[$mode];
1272    } /* }}} */
1273
1274    /**
1275     * Add access right to document
1276     *
1277     * This method may change in the future. Instead of passing a flag
1278     * and a user/group id a user or group object will be expected.
1279     * Starting with version 5.1.25 this method will first check if there
1280     * is already an access right for the user/group.
1281     *
1282     * @param integer $mode access mode
1283     * @param integer $userOrGroupID id of user or group
1284     * @param integer $isUser set to 1 if $userOrGroupID is the id of a
1285     *        user otherwise it will be considered a group id
1286     * @return bool true on success, otherwise false
1287     */
1288    public function addAccess($mode, $userOrGroupID, $isUser) { /* {{{ */
1289        $db = $this->_dms->getDB();
1290
1291        if ($mode < M_NONE || $mode > M_ALL)
1292            return false;
1293
1294        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1295
1296        /* Adding a second access right will return false */
1297        $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT.
1298                " AND `target` = " . $this->_id . " AND ". $userOrGroup . " = ".$userOrGroupID;
1299        $resArr = $db->getResultArray($queryStr);
1300        if (is_bool($resArr) || $resArr)
1301            return false;
1302
1303        $queryStr = "INSERT INTO `tblACLs` (`target`, `targetType`, ".$userOrGroup.", `mode`) VALUES
1304                    (".$this->_id.", ".T_DOCUMENT.", " . (int) $userOrGroupID . ", " .(int) $mode. ")";
1305        if (!$db->getResult($queryStr))
1306            return false;
1307
1308        unset($this->_accessList);
1309        $this->_readAccessList = array();
1310
1311        // Update the notify list, if necessary.
1312        if ($mode == M_NONE) {
1313            $this->removeNotify($userOrGroupID, $isUser);
1314        }
1315
1316        return true;
1317    } /* }}} */
1318
1319    /**
1320     * Change access right of document
1321     *
1322     * This method may change in the future. Instead of passing a flag
1323     * and a user/group id a user or group object will be expected.
1324     *
1325     * @param integer $newMode access mode
1326     * @param integer $userOrGroupID id of user or group
1327     * @param integer $isUser set to 1 if $userOrGroupID is the id of a
1328     *        user otherwise it will be considered a group id
1329     * @return bool true on success, otherwise false
1330     */
1331    public function changeAccess($newMode, $userOrGroupID, $isUser) { /* {{{ */
1332        $db = $this->_dms->getDB();
1333
1334        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1335
1336        /* Get the old access right */
1337        $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT.
1338                " AND `target` = " . $this->_id . " AND ". $userOrGroup . " = ". (int) $userOrGroupID;
1339        $resArr = $db->getResultArray($queryStr);
1340        if (!$resArr)
1341            return false;
1342
1343        $oldmode = $resArr[0]['mode'];
1344
1345        $queryStr = "UPDATE `tblACLs` SET `mode` = " . (int) $newMode . " WHERE `targetType` = ".T_DOCUMENT." AND `target` = " . $this->_id . " AND " . $userOrGroup . " = " . (int) $userOrGroupID;
1346        if (!$db->getResult($queryStr))
1347            return false;
1348
1349        unset($this->_accessList);
1350        $this->_readAccessList = array();
1351
1352        // Update the notify list, if necessary.
1353        if ($newMode == M_NONE) {
1354            $this->removeNotify($userOrGroupID, $isUser);
1355        }
1356
1357        return $oldmode;
1358    } /* }}} */
1359
1360    /**
1361     * Remove access rights for a user or group
1362     *
1363     * @param integer $userOrGroupID ID of user or group
1364     * @param boolean $isUser true if $userOrGroupID is a user id, false if it
1365     *        is a group id.
1366     * @return boolean true on success, otherwise false
1367     */
1368    public function removeAccess($userOrGroupID, $isUser) { /* {{{ */
1369        $db = $this->_dms->getDB();
1370
1371        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1372
1373        /* Get the old access right */
1374        $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT.
1375                " AND `target` = " . $this->_id . " AND ". $userOrGroup . " = ". (int) $userOrGroupID;
1376        $resArr = $db->getResultArray($queryStr);
1377        if (!$resArr)
1378            return false;
1379
1380        $queryStr = "DELETE FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT." AND `target` = ".$this->_id." AND ".$userOrGroup." = " . (int) $userOrGroupID;
1381        if (!$db->getResult($queryStr))
1382            return false;
1383
1384        unset($this->_accessList);
1385        $this->_readAccessList = array();
1386
1387        // Update the notify list, if the user looses access rights.
1388        $mode = ($isUser ? $this->getAccessMode($this->_dms->getUser($userOrGroupID)) : $this->getGroupAccessMode($this->_dms->getGroup($userOrGroupID)));
1389        if ($mode == M_NONE) {
1390            $this->removeNotify($userOrGroupID, $isUser);
1391        }
1392
1393        return true;
1394    } /* }}} */
1395
1396    /**
1397     * Returns the greatest access privilege for a given user
1398     *
1399     * This method returns the access mode for a given user. An administrator
1400     * and the owner of the folder has unrestricted access. A guest user has
1401     * read only access or no access if access rights are further limited
1402     * by access control lists. All other users have access rights according
1403     * to the access control lists or the default access. This method will
1404     * recursive check for access rights of parent folders if access rights
1405     * are inherited.
1406     *
1407     * The function searches the access control list for entries of
1408     * user $user. If it finds more than one entry it will return the
1409     * one allowing the greatest privileges, but user rights will always
1410     * precede group rights. If there is no entry in the
1411     * access control list, it will return the default access mode.
1412     * The function takes inherited access rights into account.
1413     * For a list of possible access rights see @file inc.AccessUtils.php
1414     *
1415     * Having access on a document does not necessarily mean the document
1416     * content is accessible too. Accessing the content is checked by
1417     * {@see SeedDMS_Core_DocumentContent::getAccessMode()} which calls
1418     * a callback function defined by the application. If the callback
1419     * function is not set, access on the content is always granted.
1420     *
1421     * Before checking the access in the method itself a callback 'onCheckAccessDocument'
1422     * is called. If it returns a value > 0, then this will be returned by this
1423     * method without any further checks. The optional paramater $context
1424     * will be passed as a third parameter to the callback. It contains
1425     * the operation for which the access mode is retrieved. It is for example
1426     * set to 'removeDocument' if the access mode is used to check for sufficient
1427     * permission on deleting a document.
1428     *
1429     * @param $user object instance of class SeedDMS_Core_User
1430     * @param string $context context in which the access mode is requested
1431     * @return integer access mode
1432     */
1433    public function getAccessMode($user, $context = '') { /* {{{ */
1434        if (!$user)
1435            return M_NONE;
1436
1437        /* Check if 'onCheckAccessDocument' callback is set */
1438        if (isset($this->_dms->callbacks['onCheckAccessDocument'])) {
1439            foreach ($this->_dms->callbacks['onCheckAccessDocument'] as $callback) {
1440                if (($ret = call_user_func($callback[0], $callback[1], $this, $user, $context)) > 0) {
1441                    return $ret;
1442                }
1443            }
1444        }
1445
1446        /* Administrators have unrestricted access */
1447        if ($user->isAdmin()) return M_ALL;
1448
1449        /* The owner of the document has unrestricted access */
1450        if ($user->getID() == $this->_ownerID) return M_ALL;
1451
1452        if ($this->_dms->memcache) {
1453            $ck = "am:d".$this->_id.":".$user->getId().":".$context;
1454            if ($cobj = $this->_dms->memcache->get($ck))
1455                return $cobj;
1456        }
1457
1458        /* Check ACLs */
1459        $accessList = $this->getAccessList();
1460        if (!$accessList) return false;
1461
1462        /** @var SeedDMS_Core_UserAccess $userAccess */
1463        foreach ($accessList["users"] as $userAccess) {
1464            if ($userAccess->getUserID() == $user->getID()) {
1465                $mode = $userAccess->getMode();
1466                if ($user->isGuest()) {
1467                    if ($mode >= M_READ) $mode = M_READ;
1468                }
1469                if ($this->_dms->memcache)
1470                     $this->_dms->memcache->set($ck, $mode, 600);
1471                return $mode;
1472            }
1473        }
1474
1475        /* Get the highest right defined by a group */
1476        if ($accessList['groups']) {
1477            $mode = 0;
1478            /** @var SeedDMS_Core_GroupAccess $groupAccess */
1479            foreach ($accessList["groups"] as $groupAccess) {
1480                if ($user->isMemberOfGroup($groupAccess->getGroup())) {
1481                    if ($groupAccess->getMode() > $mode)
1482                        $mode = $groupAccess->getMode();
1483                }
1484            }
1485            if ($mode) {
1486                if ($user->isGuest()) {
1487                    if ($mode >= M_READ) $mode = M_READ;
1488                }
1489                if ($this->_dms->memcache)
1490                     $this->_dms->memcache->set($ck, $mode, 600);
1491                return $mode;
1492            }
1493        }
1494
1495        $mode = $this->getDefaultAccess();
1496        if ($user->isGuest()) {
1497            if ($mode >= M_READ) $mode = M_READ;
1498        }
1499        if ($this->_dms->memcache)
1500             $this->_dms->memcache->set($ck, $mode, 600);
1501        return $mode;
1502    } /* }}} */
1503
1504    /**
1505     * Returns the greatest access privilege for a given group
1506     *
1507     * This method searches the access control list for entries of
1508     * group $group. If it finds more than one entry it will return the
1509     * one allowing the greatest privileges. If there is no entry in the
1510     * access control list, it will return the default access mode.
1511     * The function takes inherited access rights into account.
1512     * For a list of possible access rights see @file inc.AccessUtils.php
1513     *
1514     * @param SeedDMS_Core_Group $group object instance of class SeedDMS_Core_Group
1515     * @return integer access mode
1516     */
1517    public function getGroupAccessMode($group) { /* {{{ */
1518        $highestPrivileged = M_NONE;
1519
1520        //ACLs durchforsten
1521        $foundInACL = false;
1522        $accessList = $this->getAccessList();
1523        if (!$accessList)
1524            return false;
1525
1526        /** @var SeedDMS_Core_GroupAccess $groupAccess */
1527        foreach ($accessList["groups"] as $groupAccess) {
1528            if ($groupAccess->getGroupID() == $group->getID()) {
1529                $foundInACL = true;
1530                if ($groupAccess->getMode() > $highestPrivileged)
1531                    $highestPrivileged = $groupAccess->getMode();
1532                if ($highestPrivileged == M_ALL) // max access right -> skip the rest
1533                    return $highestPrivileged;
1534            }
1535        }
1536
1537        if ($foundInACL)
1538            return $highestPrivileged;
1539
1540        //Standard-Berechtigung verwenden
1541        return $this->getDefaultAccess();
1542    } /* }}} */
1543
1544    /**
1545     * Returns a list of all notifications
1546     *
1547     * The returned list has two elements called 'users' and 'groups'. Each one
1548     * is an array itself countaining objects of class SeedDMS_Core_User and
1549     * SeedDMS_Core_Group.
1550     *
1551     * @param integer $type type of notification (not yet used)
1552     * @param bool $incdisabled set to true if disabled user shall be included
1553     * @return array|bool
1554     */
1555    public function getNotifyList($type = 0, $incdisabled = false) { /* {{{ */
1556        if (empty($this->_notifyList)) {
1557            $db = $this->_dms->getDB();
1558
1559            $queryStr ="SELECT * FROM `tblNotify` WHERE `targetType` = " . T_DOCUMENT . " AND `target` = " . $this->_id;
1560            $resArr = $db->getResultArray($queryStr);
1561            if (is_bool($resArr) && $resArr == false)
1562                return false;
1563
1564            $this->_notifyList = array("groups" => array(), "users" => array());
1565            foreach ($resArr as $row)
1566            {
1567                if ($row["userID"] != -1) {
1568                    $u = $this->_dms->getUser($row["userID"]);
1569                    if ($u && (!$u->isDisabled() || $incdisabled))
1570                        array_push($this->_notifyList["users"], $u);
1571                } else { //if ($row["groupID"] != -1)
1572                    $g = $this->_dms->getGroup($row["groupID"]);
1573                    if ($g)
1574                        array_push($this->_notifyList["groups"], $g);
1575                }
1576            }
1577        }
1578        return $this->_notifyList;
1579    } /* }}} */
1580
1581    /**
1582     * Make sure only users/groups with read access are in the notify list
1583     *
1584     */
1585    public function cleanNotifyList() { /* {{{ */
1586        // If any of the notification subscribers no longer have read access,
1587        // remove their subscription.
1588        if (empty($this->_notifyList))
1589            $this->getNotifyList();
1590
1591        /* Make a copy of both notifier lists because removeNotify will empty
1592         * $this->_notifyList and the second foreach will not work anymore.
1593         */
1594        /** @var SeedDMS_Core_User[] $nusers */
1595        $nusers = $this->_notifyList["users"];
1596        /** @var SeedDMS_Core_Group[] $ngroups */
1597        $ngroups = $this->_notifyList["groups"];
1598        foreach ($nusers as $u) {
1599            if ($this->getAccessMode($u) < M_READ) {
1600                $this->removeNotify($u->getID(), true);
1601            }
1602        }
1603        foreach ($ngroups as $g) {
1604            if ($this->getGroupAccessMode($g) < M_READ) {
1605                $this->removeNotify($g->getID(), false);
1606            }
1607        }
1608    } /* }}} */
1609
1610    /**
1611     * Add a user/group to the notification list
1612     *
1613     * This method does not check if the currently logged in user
1614     * is allowed to add a notification. This must be checked by the calling
1615     * application.
1616     *
1617     * @param $userOrGroupID integer id of user or group to add
1618     * @param $isUser integer 1 if $userOrGroupID is a user,
1619     *                0 if $userOrGroupID is a group
1620     * @return integer  0: Update successful.
1621     *                 -1: Invalid User/Group ID.
1622     *                 -2: Target User / Group does not have read access.
1623     *                 -3: User is already subscribed.
1624     *                 -4: Database / internal error.
1625     */
1626    public function addNotify($userOrGroupID, $isUser) { /* {{{ */
1627        $db = $this->_dms->getDB();
1628
1629        $userOrGroup = ($isUser ? "`userID`" : "`groupID`");
1630
1631        /* Verify that user / group exists. */
1632        $obj = ($isUser ? $this->_dms->getUser($userOrGroupID) : $this->_dms->getGroup($userOrGroupID));
1633        if (!is_object($obj)) {
1634            return -1;
1635        }
1636
1637        /* Verify that the requesting user has permission to add the target to
1638         * the notification system.
1639         */
1640        /*
1641         * The calling application should enforce the policy on who is allowed
1642         * to add someone to the notification system. If is shall remain here
1643         * the currently logged in user should be passed to this function
1644         *
1645        GLOBAL $user;
1646        if ($user->isGuest()) {
1647            return -2;
1648        }
1649        if (!$user->isAdmin()) {
1650            if ($isUser) {
1651                if ($user->getID() != $obj->getID()) {
1652                    return -2;
1653                }
1654            }
1655            else {
1656                if (!$obj->isMember($user)) {
1657                    return -2;
1658                }
1659            }
1660        }
1661         */
1662
1663        /* Verify that target user / group has read access to the document. */
1664        if ($isUser) {
1665            // Users are straightforward to check.
1666            if ($this->getAccessMode($obj) < M_READ) {
1667                return -2;
1668            }
1669        }
1670        else {
1671            // Groups are a little more complex.
1672            if ($this->getDefaultAccess() >= M_READ) {
1673                // If the default access is at least READ-ONLY, then just make sure
1674                // that the current group has not been explicitly excluded.
1675                $acl = $this->getAccessList(M_NONE, O_EQ);
1676                $found = false;
1677                /** @var SeedDMS_Core_GroupAccess $group */
1678                foreach ($acl["groups"] as $group) {
1679                    if ($group->getGroupID() == $userOrGroupID) {
1680                        $found = true;
1681                        break;
1682                    }
1683                }
1684                if ($found) {
1685                    return -2;
1686                }
1687            }
1688            else {
1689                // The default access is restricted. Make sure that the group has
1690                // been explicitly allocated access to the document.
1691                $acl = $this->getAccessList(M_READ, O_GTEQ);
1692                if (is_bool($acl)) {
1693                    return -4;
1694                }
1695                $found = false;
1696                /** @var SeedDMS_Core_GroupAccess $group */
1697                foreach ($acl["groups"] as $group) {
1698                    if ($group->getGroupID() == $userOrGroupID) {
1699                        $found = true;
1700                        break;
1701                    }
1702                }
1703                if (!$found) {
1704                    return -2;
1705                }
1706            }
1707        }
1708        /* Check to see if user/group is already on the list. */
1709        $queryStr = "SELECT * FROM `tblNotify` WHERE `tblNotify`.`target` = '".$this->_id."' ".
1710            "AND `tblNotify`.`targetType` = '".T_DOCUMENT."' ".
1711            "AND `tblNotify`.".$userOrGroup." = '".(int) $userOrGroupID."'";
1712        $resArr = $db->getResultArray($queryStr);
1713        if (is_bool($resArr)) {
1714            return -4;
1715        }
1716        if (count($resArr)>0) {
1717            return -3;
1718        }
1719
1720        $queryStr = "INSERT INTO `tblNotify` (`target`, `targetType`, " . $userOrGroup . ") VALUES (" . $this->_id . ", " . T_DOCUMENT . ", " . (int) $userOrGroupID . ")";
1721        if (!$db->getResult($queryStr))
1722            return -4;
1723
1724        unset($this->_notifyList);
1725        return 0;
1726    } /* }}} */
1727
1728    /**
1729     * Remove a user or group from the notification list
1730     *
1731     * This method does not check if the currently logged in user
1732     * is allowed to remove a notification. This must be checked by the calling
1733     * application.
1734     *
1735     * @param integer $userOrGroupID id of user or group
1736     * @param boolean $isUser boolean true if a user is passed in $userOrGroupID, false
1737     *        if a group is passed in $userOrGroupID
1738     * @param integer $type type of notification (0 will delete all) Not used yet!
1739     * @return integer 0 if operation was succesful
1740     *                 -1 if the userid/groupid is invalid
1741     *                 -3 if the user/group is already subscribed
1742     *                 -4 in case of an internal database error
1743     */
1744    public function removeNotify($userOrGroupID, $isUser, $type = 0) { /* {{{ */
1745        $db = $this->_dms->getDB();
1746
1747        /* Verify that user / group exists. */
1748        /** @var SeedDMS_Core_Group|SeedDMS_Core_User $obj */
1749        $obj = ($isUser ? $this->_dms->getUser($userOrGroupID) : $this->_dms->getGroup($userOrGroupID));
1750        if (!is_object($obj)) {
1751            return -1;
1752        }
1753
1754        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1755
1756        /* Verify that the requesting user has permission to add the target to
1757         * the notification system.
1758         */
1759        /*
1760         * The calling application should enforce the policy on who is allowed
1761         * to add someone to the notification system. If is shall remain here
1762         * the currently logged in user should be passed to this function
1763         *
1764        GLOBAL $user;
1765        if ($user->isGuest()) {
1766            return -2;
1767        }
1768        if (!$user->isAdmin()) {
1769            if ($isUser) {
1770                if ($user->getID() != $obj->getID()) {
1771                    return -2;
1772                }
1773            }
1774            else {
1775                if (!$obj->isMember($user)) {
1776                    return -2;
1777                }
1778            }
1779        }
1780         */
1781
1782        /* Check to see if the target is in the database. */
1783        $queryStr = "SELECT * FROM `tblNotify` WHERE `tblNotify`.`target` = '".$this->_id."' ".
1784            "AND `tblNotify`.`targetType` = '".T_DOCUMENT."' ".
1785            "AND `tblNotify`.".$userOrGroup." = '".(int) $userOrGroupID."'";
1786        $resArr = $db->getResultArray($queryStr);
1787        if (is_bool($resArr)) {
1788            return -4;
1789        }
1790        if (count($resArr)==0) {
1791            return -3;
1792        }
1793
1794        $queryStr = "DELETE FROM `tblNotify` WHERE `target` = " . $this->_id . " AND `targetType` = " . T_DOCUMENT . " AND " . $userOrGroup . " = " . (int) $userOrGroupID;
1795        /* If type is given then delete only those notifications */
1796        if ($type)
1797            $queryStr .= " AND `type` = ".(int) $type;
1798        if (!$db->getResult($queryStr))
1799            return -4;
1800
1801        unset($this->_notifyList);
1802        return 0;
1803    } /* }}} */
1804
1805    /**
1806     * Add content to a document
1807     *
1808     * Each document may have any number of content elements attached to it.
1809     * Each content element has a version number. Newer versions (greater
1810     * version number) replace older versions.
1811     *
1812     * @param string $comment comment
1813     * @param object $user user who shall be the owner of this content
1814     * @param string $tmpFile file containing the actuall content
1815     * @param string $orgFileName original file name
1816     * @param string $fileType
1817     * @param string $mimeType MimeType of the content
1818     * @param array $reviewers list of reviewers
1819     * @param array $approvers list of approvers
1820     * @param integer $version version number of content or 0 if next higher version shall be used.
1821     * @param array $attributes list of version attributes. The element key
1822     *        must be the id of the attribute definition.
1823     * @param object $workflow
1824     * @return bool|SeedDMS_Core_AddContentResultSet
1825     */
1826    public function addContent($comment, $user, $tmpFile, $orgFileName, $fileType, $mimeType, $reviewers = array(), $approvers = array(), $version = 0, $attributes = array(), $workflow = null) { /* {{{ */
1827        $db = $this->_dms->getDB();
1828
1829        // the doc path is id/version.filetype
1830        $dir = $this->getDir();
1831
1832        /* The version field in table tblDocumentContent used to be auto
1833         * increment but that requires the field to be primary as well if
1834         * innodb is used. That's why the version is now determined here.
1835         */
1836        if ((int)$version<1) {
1837            $queryStr = "SELECT MAX(`version`) AS m FROM `tblDocumentContent` WHERE `document` = ".$this->_id;
1838            $resArr = $db->getResultArray($queryStr);
1839            if (is_bool($resArr) && !$resArr)
1840                return false;
1841
1842            $version = $resArr[0]['m']+1;
1843        }
1844
1845        if ($fileType == '.')
1846            $fileType = '';
1847        $filesize = SeedDMS_Core_File::fileSize($tmpFile);
1848        $checksum = SeedDMS_Core_File::checksum($tmpFile);
1849
1850        $db->startTransaction();
1851        $queryStr = "INSERT INTO `tblDocumentContent` (`document`, `version`, `comment`, `date`, `createdBy`, `dir`, `orgFileName`, `fileType`, `mimeType`, `fileSize`, `checksum`) VALUES ".
1852            "(".$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).")";
1853        if (!$db->getResult($queryStr)) {
1854            $db->rollbackTransaction();
1855            return false;
1856        }
1857
1858        $contentID = $db->getInsertID('tblDocumentContent');
1859        $content = $this->_dms->getDocumentContent($contentID);
1860
1861        if ($storage = $this->_dms->getStorage()) {
1862            if (!$storage->saveContent($this, $content, $tmpFile)) {
1863                $db->rollbackTransaction();
1864                return false;
1865            }
1866        } else {
1867        // copy file
1868        if (!SeedDMS_Core_File::makeDir($this->_dms->contentDir . $dir)) {
1869            $db->rollbackTransaction();
1870            return false;
1871        }
1872        if ($this->_dms->forceRename)
1873            $err = SeedDMS_Core_File::renameFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType);
1874        elseif ($this->_dms->forceLink)
1875            $err = SeedDMS_Core_File::linkFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType);
1876        else
1877            $err = SeedDMS_Core_File::copyFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType);
1878        if (!$err) {
1879            $db->rollbackTransaction();
1880            return false;
1881        }
1882        }
1883
1884        $this->_content = null;
1885        $this->_latestContent = null;
1886        $docResultSet = new SeedDMS_Core_AddContentResultSet($content);
1887        $docResultSet->setDMS($this->_dms);
1888
1889        if ($attributes) {
1890            foreach ($attributes as $attrdefid => $attribute) {
1891                /* $attribute can be a string or an array */
1892                if ($attribute) {
1893                    if ($attrdef = $this->_dms->getAttributeDefinition($attrdefid)) {
1894                        if (!$content->setAttributeValue($attrdef, $attribute)) {
1895                            $this->_removeContent($content);
1896                            $db->rollbackTransaction();
1897                            return false;
1898                        }
1899                    } else {
1900                        $this->_removeContent($content);
1901                        $db->rollbackTransaction();
1902                        return false;
1903                    }
1904                }
1905            }
1906        }
1907
1908        $queryStr = "INSERT INTO `tblDocumentStatus` (`documentID`, `version`) ".
1909            "VALUES (". $this->_id .", ". (int) $version .")";
1910        if (!$db->getResult($queryStr)) {
1911            $this->_removeContent($content);
1912            $db->rollbackTransaction();
1913            return false;
1914        }
1915
1916        $statusID = $db->getInsertID('tblDocumentStatus', 'statusID');
1917
1918        if ($workflow)
1919            $content->setWorkflow($workflow, $user);
1920
1921        // Add reviewers into the database. Reviewers must review the document
1922        // and submit comments, if appropriate. Reviewers can also recommend that
1923        // a document be rejected.
1924        $pendingReview = false;
1925        /** @noinspection PhpUnusedLocalVariableInspection */
1926        foreach (array("i", "g") as $i) {
1927            if (isset($reviewers[$i])) {
1928                foreach ($reviewers[$i] as $reviewerID) {
1929                    $reviewer = ($i == "i" ?$this->_dms->getUser($reviewerID) : $this->_dms->getGroup($reviewerID));
1930                    $res = ($i == "i" ? $docResultSet->getContent()->addIndReviewer($reviewer, $user, true) : $docResultSet->getContent()->addGrpReviewer($reviewer, $user, true));
1931                    $docResultSet->addReviewer($reviewer, $i, $res);
1932                    // res is the id of the record in the database
1933                    if ($res > 0 || $res == -3) {
1934                        $pendingReview = true;
1935                    }
1936                }
1937            }
1938        }
1939        // Add approvers to the database. Approvers must also review the document
1940        // and make a recommendation on its release as an approved version.
1941        $pendingApproval = false;
1942        /** @noinspection PhpUnusedLocalVariableInspection */
1943        foreach (array("i", "g") as $i) {
1944            if (isset($approvers[$i])) {
1945                foreach ($approvers[$i] as $approverID) {
1946                    $approver = ($i == "i" ? $this->_dms->getUser($approverID) : $this->_dms->getGroup($approverID));
1947                    $res = ($i == "i" ? $docResultSet->getContent()->addIndApprover($approver, $user, true) : $docResultSet->getContent()->addGrpApprover($approver, $user, !$pendingReview));
1948                    $docResultSet->addApprover($approver, $i, $res);
1949                    // res is the id of the record in the database
1950                    if ($res > 0 || $res == -3) {
1951                        $pendingApproval = true;
1952                    }
1953                }
1954            }
1955        }
1956
1957        // If there are no reviewers or approvers, the document is automatically
1958        // promoted to the released state.
1959        if ($pendingReview) {
1960            $status = S_DRAFT_REV;
1961            $comment = "";
1962        }
1963        elseif ($pendingApproval) {
1964            $status = S_DRAFT_APP;
1965            $comment = "";
1966        }
1967        elseif ($workflow) {
1968            $status = S_IN_WORKFLOW;
1969            $comment = ", workflow: ".$workflow->getName();
1970        } else {
1971            $status = S_RELEASED;
1972            $comment = "";
1973        }
1974        $queryStr = "INSERT INTO `tblDocumentStatusLog` (`statusID`, `status`, `comment`, `date`, `userID`) ".
1975            "VALUES ('". $statusID ."', '". $status."', 'New document content submitted". $comment ."', ".$db->getCurrentDatetime().", '". $user->getID() ."')";
1976        if (!$db->getResult($queryStr)) {
1977            $db->rollbackTransaction();
1978            return false;
1979        }
1980
1981        /** @noinspection PhpMethodParametersCountMismatchInspection */
1982        $docResultSet->setStatus($status);
1983
1984        $db->commitTransaction();
1985        return $docResultSet;
1986    } /* }}} */
1987
1988    /**
1989     * Replace a version of a document
1990     *
1991     * Each document may have any number of content elements attached to it.
1992     * This method replaces the file content of a given version.
1993     * Using this function is highly discourage, because it undermines the
1994     * idea of keeping all versions of a document as originally saved.
1995     * Content will only be replaced if the mimetype, filetype, user and
1996     * original filename are identical to the version being updated.
1997     *
1998     * This method was introduced for the webdav server because any saving
1999     * of a document created a new version.
2000     *
2001     * @param object $user user who shall be the owner of this content
2002     * @param string $tmpFile file containing the actuall content
2003     * @param string $orgFileName original file name
2004     * @param string $fileType
2005     * @param string $mimeType MimeType of the content
2006     * @param integer $version version number of content or 0 if latest version shall be replaced.
2007     * @return bool/array false in case of an error or a result set
2008     */
2009    public function replaceContent($version, $user, $tmpFile, $orgFileName, $fileType, $mimeType, $allowoverride = []) { /* {{{ */
2010        $db = $this->_dms->getDB();
2011
2012        // the doc path is id/version.filetype
2013        $dir = $this->getDir();
2014
2015        /* If $version < 1 than replace the content of the latest version.
2016         */
2017        if ((int) $version<1) {
2018            $queryStr = "SELECT MAX(`version`) AS m FROM `tblDocumentContent` WHERE `document` = ".$this->_id;
2019            $resArr = $db->getResultArray($queryStr);
2020            if (is_bool($resArr) && !$resArr)
2021                return false;
2022
2023            $version = $resArr[0]['m'];
2024        }
2025
2026        $content = $this->getContentByVersion($version);
2027        if (!$content)
2028            return false;
2029
2030        if ($fileType == '.')
2031            $fileType = '';
2032
2033        $sql = [];
2034        /* Check if $user, $orgFileName, $fileType and $mimeType are the same */
2035        if ($user->getID() != $content->getUser()->getID()) {
2036            if (!empty($allowoverride['user']))
2037                $sql[] = "`createdBy`=".$user->getID();
2038            else
2039                return false;
2040        }
2041        if ($orgFileName != $content->getOriginalFileName()) {
2042            if (!empty($allowoverride['orgfilename']))
2043                $sql[] = "`orgFileName`=".$db->qstr($orgFileName);
2044            else
2045                return false;
2046        }
2047        if ($fileType != $content->getFileType()) {
2048            if (!empty($allowoverride['filetype']))
2049                $sql[] = "`fileType`=".$db->qstr($fileType);
2050            else
2051                return false;
2052        }
2053        if ($mimeType != $content->getMimeType()) {
2054            if (!empty($allowoverride['mimetype']))
2055                $sql[] = "`mimeType`=".$db->qstr($mimeType);
2056            else
2057                return false;
2058        }
2059
2060        $filesize = SeedDMS_Core_File::fileSize($tmpFile);
2061        $checksum = SeedDMS_Core_File::checksum($tmpFile);
2062
2063        $db->startTransaction();
2064        $sql[] = "`date`=".$db->getCurrentTimestamp();
2065        $sql[] = "`fileSize`=".$filesize;
2066        $sql[] = "`checksum`=".$db->qstr($checksum);
2067        $queryStr = "UPDATE `tblDocumentContent` set ".implode(", ", $sql)." WHERE `id`=".$content->getID();
2068        if (!$db->getResult($queryStr)) {
2069            $db->rollbackTransaction();
2070            return false;
2071        }
2072
2073        if ($storage = $this->_dms->getStorage()) {
2074            if (!$storage->replaceContent($this, $content, $tmpFile)) {
2075                $db->rollbackTransaction();
2076                return false;
2077            }
2078        } else {
2079        // copy file
2080            if (!SeedDMS_Core_File::copyFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType)) {
2081                $db->rollbackTransaction();
2082                return false;
2083            }
2084        }
2085
2086        $this->_content = null;
2087        $this->_latestContent = null;
2088        $db->commitTransaction();
2089
2090        return true;
2091    } /* }}} */
2092
2093    /**
2094     * Return all content elements of a document
2095     *
2096     * This method returns an array of content elements ordered by version.
2097     * Version which are not accessible because of its status, will be filtered
2098     * out. Access rights based on the document status are calculated for the
2099     * currently logged in user.
2100     *
2101     * @return bool|SeedDMS_Core_DocumentContent[]
2102     */
2103    public function getContent() { /* {{{ */
2104        $db = $this->_dms->getDB();
2105
2106        if (!isset($this->_content)) {
2107            $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." ORDER BY `version`";
2108            $resArr = $db->getResultArray($queryStr);
2109            if (is_bool($resArr) && !$resArr)
2110                return false;
2111
2112            $this->_content = array();
2113            $classname = $this->_dms->getClassname('documentcontent');
2114            $user = $this->_dms->getLoggedInUser();
2115            foreach ($resArr as $row) {
2116                /** @var SeedDMS_Core_DocumentContent $content */
2117                $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']);
2118                /* TODO: Better use content id as key in $this->_content. This
2119                 * would allow to remove a single content object in removeContent().
2120                 * Currently removeContent() must clear $this->_content completely
2121                 */
2122                if ($user) {
2123                    if ($content->getAccessMode($user) >= M_READ)
2124                        array_push($this->_content, $content);
2125                } else {
2126                    array_push($this->_content, $content);
2127                }
2128            }
2129        }
2130
2131        return $this->_content;
2132    } /* }}} */
2133
2134    /**
2135     * Return the content element of a document with a given version number
2136     *
2137     * This method will check if the version is accessible and return false
2138     * if not. Access rights based on the document status are calculated for the
2139     * currently logged in user.
2140     *
2141     * @param integer $version version number of content element
2142     * @return SeedDMS_Core_DocumentContent|null|boolean object of class
2143     * {@see SeedDMS_Core_DocumentContent}, null if not content was found,
2144     * false in case of an error
2145     */
2146    public function getContentByVersion($version) { /* {{{ */
2147        if (!is_numeric($version)) return false;
2148
2149        if (isset($this->_content)) {
2150            foreach ($this->_content as $revision) {
2151                if ($revision->getVersion() == $version)
2152                    return $revision;
2153            }
2154            return null;
2155        }
2156
2157        $db = $this->_dms->getDB();
2158        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." AND `version` = " . (int) $version;
2159        $resArr = $db->getResultArray($queryStr);
2160        if (is_bool($resArr) && !$resArr)
2161            return false;
2162        if (count($resArr) != 1)
2163            return null;
2164
2165        $resArr = $resArr[0];
2166        $classname = $this->_dms->getClassname('documentcontent');
2167        /** @var SeedDMS_Core_DocumentContent $content */
2168        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'])) {
2169            $user = $this->_dms->getLoggedInUser();
2170            /* A user with write access on the document may always see the version */
2171            if ($user && $content->getAccessMode($user) == M_NONE)
2172                return null;
2173            else
2174                return $content;
2175        } else {
2176            return false;
2177        }
2178    } /* }}} */
2179
2180    /**
2181     * Check if a given version is the latest version of the document
2182     *
2183     * @param integer $version version number of content element
2184     * @return SeedDMS_Core_DocumentContent|boolean object of class {@see SeedDMS_Core_DocumentContent}
2185     * or false
2186     */
2187    public function isLatestContent($version) { /* {{{ */
2188        return $this->getLatestContent()->getVersion() == $version;
2189    } /* }}} */
2190
2191    /**
2192     * @return bool|null|SeedDMS_Core_DocumentContent
2193     */
2194    private function __getLatestContent() { /* {{{ */
2195        if (!$this->_latestContent) {
2196            $db = $this->_dms->getDB();
2197            $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." ORDER BY `version` DESC LIMIT 1";
2198            $resArr = $db->getResultArray($queryStr);
2199            if (is_bool($resArr) && !$resArr)
2200                return false;
2201            if (count($resArr) != 1)
2202                return false;
2203
2204            $resArr = $resArr[0];
2205            $classname = $this->_dms->getClassname('documentcontent');
2206            $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']);
2207        }
2208        return $this->_latestContent;
2209    } /* }}} */
2210
2211    /**
2212     * Get the latest version of document
2213     *
2214     * This method returns the latest accessible version of a document.
2215     * If content access has been restricted by setting
2216     * {@see SeedDMS_Core_DMS::noReadForStatus} the function will go
2217     * backwards in history until an accessible version is found. If none
2218     * is found null will be returned.
2219     * Access rights based on the document status are calculated for the
2220     * currently logged in user.
2221     *
2222     * @return bool|SeedDMS_Core_DocumentContent object of class {@see SeedDMS_Core_DocumentContent}
2223     */
2224    public function getLatestContent() { /* {{{ */
2225        if (!$this->_latestContent) {
2226            $db = $this->_dms->getDB();
2227            $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." ORDER BY `version` DESC";
2228            $resArr = $db->getResultArray($queryStr);
2229            if (is_bool($resArr) && !$resArr)
2230                return false;
2231
2232            $classname = $this->_dms->getClassname('documentcontent');
2233            $user = $this->_dms->getLoggedInUser();
2234            foreach ($resArr as $row) {
2235                if (!$this->_latestContent) {
2236                    /** @var SeedDMS_Core_DocumentContent $content */
2237                    $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']);
2238                    if ($user) {
2239                        /* If the user may even write the document, then also allow to see all content.
2240                         * This is needed because the user could upload a new version
2241                         */
2242                        if ($content->getAccessMode($user) >= M_READ) {
2243                            $this->_latestContent = $content;
2244                        }
2245                    } else {
2246                        $this->_latestContent = $content;
2247                    }
2248                }
2249            }
2250        }
2251
2252        return $this->_latestContent;
2253    } /* }}} */
2254
2255    /**
2256     * Remove version of document
2257     *
2258     * @param SeedDMS_Core_DocumentContent $version version number of content
2259     * @return boolean true if successful, otherwise false
2260     */
2261    private function _removeContent($version) { /* {{{ */
2262        $db = $this->_dms->getDB();
2263
2264        $db->startTransaction();
2265
2266        $status = $version->getStatus();
2267        $stID = $status["statusID"];
2268
2269        $queryStr = "DELETE FROM `tblDocumentContent` WHERE `document` = " . $this->getID() . " AND `version` = " . $version->getVersion();
2270        if (!$db->getResult($queryStr)) {
2271            $db->rollbackTransaction();
2272            return false;
2273        }
2274
2275        $queryStr = "DELETE FROM `tblDocumentContentAttributes` WHERE `content` = " . $version->getId();
2276        if (!$db->getResult($queryStr)) {
2277            $db->rollbackTransaction();
2278            return false;
2279        }
2280
2281        $queryStr = "DELETE FROM `tblDocumentStatusLog` WHERE `statusID` = '".$stID."'";
2282        if (!$db->getResult($queryStr)) {
2283            $db->rollbackTransaction();
2284            return false;
2285        }
2286
2287        $queryStr = "DELETE FROM `tblDocumentStatus` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2288        if (!$db->getResult($queryStr)) {
2289            $db->rollbackTransaction();
2290            return false;
2291        }
2292
2293        $status = $version->getReviewStatus();
2294        $stList = "";
2295        foreach ($status as $st) {
2296            $stList .= (strlen($stList)==0 ? "" : ", "). "'".$st["reviewID"]."'";
2297            $queryStr = "SELECT * FROM `tblDocumentReviewLog` WHERE `reviewID` = " . $st['reviewID'];
2298            $resArr = $db->getResultArray($queryStr);
2299            if ((is_bool($resArr) && !$resArr)) {
2300                $db->rollbackTransaction();
2301                return false;
2302            }
2303            foreach ($resArr as $res) {
2304                if ($storage = $this->_dms->getStorage()) {
2305                    $storage->deleteReview($this, $res['reviewLogID']);
2306                } else {
2307                $file = $this->_dms->contentDir . $this->getDir().'r'.$res['reviewLogID'];
2308                if (SeedDMS_Core_File::file_exists($file))
2309                    SeedDMS_Core_File::removeFile($file);
2310                }
2311            }
2312        }
2313
2314        if (strlen($stList)>0) {
2315            $queryStr = "DELETE FROM `tblDocumentReviewLog` WHERE `tblDocumentReviewLog`.`reviewID` IN (".$stList.")";
2316            if (!$db->getResult($queryStr)) {
2317                $db->rollbackTransaction();
2318                return false;
2319            }
2320        }
2321        $queryStr = "DELETE FROM `tblDocumentReviewers` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2322        if (!$db->getResult($queryStr)) {
2323            $db->rollbackTransaction();
2324            return false;
2325        }
2326        $status = $version->getApprovalStatus();
2327        $stList = "";
2328        foreach ($status as $st) {
2329            $stList .= (strlen($stList)==0 ? "" : ", "). "'".$st["approveID"]."'";
2330            $queryStr = "SELECT * FROM `tblDocumentApproveLog` WHERE `approveID` = " . $st['approveID'];
2331            $resArr = $db->getResultArray($queryStr);
2332            if ((is_bool($resArr) && !$resArr)) {
2333                $db->rollbackTransaction();
2334                return false;
2335            }
2336            foreach ($resArr as $res) {
2337                if ($storage = $this->_dms->getStorage()) {
2338                    $storage->deleteApproval($this, $res['approveLogID']);
2339                } else {
2340                $file = $this->_dms->contentDir . $this->getDir().'a'.$res['approveLogID'];
2341                if (SeedDMS_Core_File::file_exists($file))
2342                    SeedDMS_Core_File::removeFile($file);
2343                }
2344            }
2345        }
2346
2347        if (strlen($stList)>0) {
2348            $queryStr = "DELETE FROM `tblDocumentApproveLog` WHERE `tblDocumentApproveLog`.`approveID` IN (".$stList.")";
2349            if (!$db->getResult($queryStr)) {
2350                $db->rollbackTransaction();
2351                return false;
2352            }
2353        }
2354        $queryStr = "DELETE FROM `tblDocumentApprovers` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2355        if (!$db->getResult($queryStr)) {
2356            $db->rollbackTransaction();
2357            return false;
2358        }
2359
2360        $queryStr = "DELETE FROM `tblWorkflowDocumentContent` WHERE `document` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2361        if (!$db->getResult($queryStr)) {
2362            $db->rollbackTransaction();
2363            return false;
2364        }
2365
2366        $queryStr = "DELETE FROM `tblWorkflowLog` WHERE `document` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2367        if (!$db->getResult($queryStr)) {
2368            $db->rollbackTransaction();
2369            return false;
2370        }
2371
2372        // remove only those document files attached to version
2373        $res = $this->getDocumentFiles($version->getVersion(), false);
2374        if (is_bool($res) && !$res) {
2375            $db->rollbackTransaction();
2376            return false;
2377        }
2378
2379        foreach ($res as $documentfile)
2380            if (!$this->removeDocumentFile($documentfile->getId())) {
2381                $db->rollbackTransaction();
2382                return false;
2383            }
2384
2385        if ($storage = $this->_dms->getStorage()) {
2386            if (!$storage->deleteContent($this, $version)) {
2387                $db->rollbackTransaction();
2388                return false;
2389            }
2390        } else {
2391        if (SeedDMS_Core_File::file_exists($this->_dms->contentDir.$version->getPath()))
2392            if (!SeedDMS_Core_File::removeFile($this->_dms->contentDir.$version->getPath())) {
2393                $db->rollbackTransaction();
2394                return false;
2395            }
2396        }
2397
2398        $db->commitTransaction();
2399        return true;
2400    } /* }}} */
2401
2402    /**
2403     * Call callback onPreRemoveDocument before deleting content
2404     *
2405     * @param SeedDMS_Core_DocumentContent $version version number of content
2406     * @return bool|mixed
2407     */
2408    public function removeContent($version) { /* {{{ */
2409        $this->_dms->lasterror = '';
2410        $db = $this->_dms->getDB();
2411
2412        /* Make sure the version exists */
2413        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = " . $this->getID() . " AND `version` = " . $version->getVersion();
2414        $resArr = $db->getResultArray($queryStr);
2415        if (is_bool($resArr) && !$resArr)
2416            return false;
2417        if (count($resArr)==0)
2418            return false;
2419
2420        /* Make sure this is not the last version */
2421        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = " . $this->getID();
2422        $resArr = $db->getResultArray($queryStr);
2423        if (is_bool($resArr) && !$resArr)
2424            return false;
2425        if (count($resArr)==1)
2426            return false;
2427
2428        /* Check if 'onPreRemoveDocument' callback is set */
2429        if (isset($this->_dms->callbacks['onPreRemoveContent'])) {
2430            foreach ($this->_dms->callbacks['onPreRemoveContent'] as $callback) {
2431                $ret = call_user_func($callback[0], $callback[1], $this, $version);
2432                if (is_bool($ret))
2433                    return $ret;
2434            }
2435        }
2436
2437        if (false === ($ret = self::_removeContent($version))) {
2438            return false;
2439        }
2440
2441        /* Invalidate the content list and the latest content of this document,
2442         * otherwise getContent() and getLatestContent()
2443         * will still return the content just deleted.
2444         */
2445        $this->_latestContent = null;
2446        $this->_content = null;
2447
2448        /* Check if 'onPostRemoveDocument' callback is set */
2449        if (isset($this->_dms->callbacks['onPostRemoveContent'])) {
2450            foreach ($this->_dms->callbacks['onPostRemoveContent'] as $callback) {
2451                if (!call_user_func($callback[0], $callback[1], $version)) {
2452                }
2453            }
2454        }
2455
2456        return $ret;
2457    } /* }}} */
2458
2459    /**
2460     * Return a certain document link
2461     *
2462     * @param integer $linkID id of link
2463     * @return SeedDMS_Core_DocumentLink|bool of SeedDMS_Core_DocumentLink or false in case of
2464     *         an error.
2465     */
2466    public function getDocumentLink($linkID) { /* {{{ */
2467        $db = $this->_dms->getDB();
2468
2469        if (!is_numeric($linkID)) return false;
2470
2471        $queryStr = "SELECT * FROM `tblDocumentLinks` WHERE `document` = " . $this->_id ." AND `id` = " . (int) $linkID;
2472        $resArr = $db->getResultArray($queryStr);
2473        if (is_bool($resArr) && !$resArr)
2474            return false;
2475        if (count($resArr)==0)
2476            return null;
2477
2478        $resArr = $resArr[0];
2479        $document = $this->_dms->getDocument($resArr["document"]);
2480        $target = $this->_dms->getDocument($resArr["target"]);
2481        if ($document && $target) {
2482            $link = new SeedDMS_Core_DocumentLink($resArr["id"], $document, $target, $resArr["userID"], $resArr["public"]);
2483            $user = $this->_dms->getLoggedInUser();
2484            if ($link->getAccessMode($user, $document, $target) >= M_READ)
2485                return $link;
2486        }
2487        return null;
2488    } /* }}} */
2489
2490    /**
2491     * Return all document links
2492     *
2493     * The list may contain all links to other documents, even those which
2494     * may not be visible by certain users, unless you pass appropriate
2495     * parameters to filter out public links and those created by
2496     * the given user. The two parameters are or'ed. If $publiconly
2497     * is set the method will return all public links disregarding the
2498     * user. If $publiconly is not set but a user is set, the method
2499     * will return all links of that user (public and none public).
2500     * Setting a user and $publiconly to true will *not* return the
2501     * public links of that user but all links which are public or
2502     * owned by that user.
2503     *
2504     * The application must call
2505     * SeedDMS_Core_DMS::filterDocumentLinks() afterwards to filter out
2506     * those links pointing to a document not accessible by a given user.
2507     *
2508     * @param boolean           $publiconly return all publically visible links
2509     * @param SeedDMS_Core_User $user       return also private links of this user
2510     *
2511     * @return array list of objects of class {@see SeedDMS_Core_DocumentLink}
2512     */
2513    public function getDocumentLinks($publiconly = false, $user = null) { /* {{{ */
2514        if (!isset($this->_documentLinks)) {
2515            $db = $this->_dms->getDB();
2516
2517            $queryStr = "SELECT * FROM `tblDocumentLinks` WHERE `document` = " . $this->_id;
2518            $tmp = array();
2519            if ($publiconly)
2520                $tmp[] = "`public`=1";
2521            if ($user)
2522                $tmp[] = "`userID`=".$user->getID();
2523            if ($tmp) {
2524                $queryStr .= " AND (".implode(" OR ", $tmp).")";
2525            }
2526
2527            $resArr = $db->getResultArray($queryStr);
2528            if (is_bool($resArr) && !$resArr)
2529                return false;
2530            $this->_documentLinks = array();
2531
2532            $user = $this->_dms->getLoggedInUser();
2533            foreach ($resArr as $row) {
2534                $target = $this->_dms->getDocument($row["target"]);
2535                if ($target) {
2536                    $link = new SeedDMS_Core_DocumentLink($row["id"], $this, $target, $row["userID"], $row["public"]);
2537                    if ($link->getAccessMode($user, $this, $target) >= M_READ)
2538                        array_push($this->_documentLinks, $link);
2539                }
2540            }
2541        }
2542        return $this->_documentLinks;
2543    } /* }}} */
2544
2545    /**
2546     * Return all document having a link on this document
2547     *
2548     * The list contains all documents which have a link to the current
2549     * document. The list contains even those documents which
2550     * may not be accessible by the user, unless you pass appropriate
2551     * parameters to filter out public links and those created by
2552     * the given user.
2553     * This method is basically the reverse of
2554     * {@see SeedDMS_Core_Document::getDocumentLinks()}
2555     *
2556     * The application must call
2557     * SeedDMS_Core_DMS::filterDocumentLinks() afterwards to filter out
2558     * those links pointing to a document not accessible by a given user.
2559     *
2560     * @param boolean           $publiconly return all publically visible links
2561     * @param SeedDMS_Core_User $user       return also private links of this user
2562     *
2563     * @return array list of objects of class SeedDMS_Core_DocumentLink
2564     */
2565    public function getReverseDocumentLinks($publiconly = false, $user = null) { /* {{{ */
2566        $db = $this->_dms->getDB();
2567
2568        $queryStr = "SELECT * FROM `tblDocumentLinks` WHERE `target` = " . $this->_id;
2569        $tmp = array();
2570        if ($publiconly)
2571            $tmp[] = "`public`=1";
2572        if ($user)
2573            $tmp[] = "`userID`=".$user->getID();
2574        if ($tmp) {
2575            $queryStr .= " AND (".implode(" OR ", $tmp).")";
2576        }
2577
2578        $resArr = $db->getResultArray($queryStr);
2579        if (is_bool($resArr) && !$resArr)
2580            return false;
2581
2582        $links = array();
2583        foreach ($resArr as $row) {
2584            $document = $this->_dms->getDocument($row["document"]);
2585            $link = new SeedDMS_Core_DocumentLink($row["id"], $document, $this, $row["userID"], $row["public"]);
2586            if ($link->getAccessMode($user, $document, $this) >= M_READ)
2587                array_push($links, $link);
2588        }
2589
2590        return $links;
2591    } /* }}} */
2592
2593    /**
2594     * Add a link to a target document
2595     *
2596     * @param int $targetID Id of target document
2597     * @param int $userID Id of user adding the link
2598     * @param boolean true if link is public
2599     * @return SeedDMS_Core_DocumentLink|boolean
2600     */
2601    public function addDocumentLink($targetID, $userID, $public) { /* {{{ */
2602        $db = $this->_dms->getDB();
2603
2604        $public = ($public) ? 1 : 0;
2605
2606        if (!is_numeric($targetID) || $targetID < 1)
2607            return false;
2608
2609        if ($targetID == $this->_id)
2610            return false;
2611
2612        if (!is_numeric($userID) || $userID < 1)
2613            return false;
2614
2615        if (!($target = $this->_dms->getDocument($targetID)))
2616            return false;
2617
2618        if (!($user = $this->_dms->getUser($userID)))
2619            return false;
2620
2621        $queryStr = "INSERT INTO `tblDocumentLinks` (`document`, `target`, `userID`, `public`) VALUES (".$this->_id.", ".(int)$targetID.", ".(int)$userID.", ".$public.")";
2622        if (!$db->getResult($queryStr))
2623            return false;
2624
2625        unset($this->_documentLinks);
2626
2627        $id = $db->getInsertID('tblDocumentLinks');
2628        $link = new SeedDMS_Core_DocumentLink($id, $this, $target, $user->getId(), $public);
2629        return $link;
2630    } /* }}} */
2631
2632    public function removeDocumentLink($linkID) { /* {{{ */
2633        $db = $this->_dms->getDB();
2634
2635        if (!is_numeric($linkID) || $linkID < 1)
2636            return false;
2637
2638        $queryStr = "DELETE FROM `tblDocumentLinks` WHERE `document` = " . $this->_id ." AND `id` = " . (int) $linkID;
2639        if (!$db->getResult($queryStr)) return false;
2640        unset($this->_documentLinks);
2641        return true;
2642    } /* }}} */
2643
2644    /**
2645     * Get attached file by its id
2646     *
2647     * @return object instance of SeedDMS_Core_DocumentFile, null if file is not
2648     * accessible, false in case of an sql error
2649     */
2650    public function getDocumentFile($ID) { /* {{{ */
2651        $db = $this->_dms->getDB();
2652
2653        if (!is_numeric($ID)) return false;
2654
2655        $queryStr = "SELECT * FROM `tblDocumentFiles` WHERE `document` = " . $this->_id ." AND `id` = " . (int) $ID;
2656        $resArr = $db->getResultArray($queryStr);
2657        if ((is_bool($resArr) && !$resArr) || count($resArr)==0) return false;
2658
2659        $resArr = $resArr[0];
2660        $classname = $this->_dms->getClassname('documentfile');
2661        $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"]);
2662        $user = $this->_dms->getLoggedInUser();
2663        if ($file->getAccessMode($user) >= M_READ)
2664            return $file;
2665        return null;
2666    } /* }}} */
2667
2668    /**
2669     * Get list of files attached to document
2670     *
2671     * @param integer $version      get only attachments for this version
2672     * @param boolean $incnoversion include attachments without a version
2673     *
2674     * @return array list of files, false in case of an sql error
2675     */
2676    public function getDocumentFiles($version = 0, $incnoversion = true) { /* {{{ */
2677        /* use a smarter caching because removing a document will call this function
2678         * for each version and the document itself.
2679         */
2680        $hash = substr(md5($version.$incnoversion), 0, 4);
2681        if (!isset($this->_documentFiles[$hash])) {
2682            $db = $this->_dms->getDB();
2683
2684            $queryStr = "SELECT * FROM `tblDocumentFiles` WHERE `document` = " . $this->_id;
2685            if ($version) {
2686                if ($incnoversion)
2687                    $queryStr .= " AND (`version`=0 OR `version`=".(int) $version.")";
2688                else
2689                    $queryStr .= " AND (`version`=".(int) $version.")";
2690            }
2691            $queryStr .= " ORDER BY ";
2692            if ($version) {
2693                $queryStr .= "`version` DESC,";
2694            }
2695            $queryStr .= "`date` DESC";
2696            $resArr = $db->getResultArray($queryStr);
2697            if (is_bool($resArr) && !$resArr) return false;
2698
2699            $this->_documentFiles = array($hash => array());
2700
2701            $user = $this->_dms->getLoggedInUser();
2702            $classname = $this->_dms->getClassname('documentfile');
2703            foreach ($resArr as $row) {
2704                $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"]);
2705                if ($file->getAccessMode($user) >= M_READ)
2706                    array_push($this->_documentFiles[$hash], $file);
2707            }
2708        }
2709        return $this->_documentFiles[$hash];
2710    } /* }}} */
2711
2712    /**
2713     * Add an attachment to the document
2714     *
2715     */
2716    public function addDocumentFile($name, $comment, $user, $tmpFile, $orgFileName, $fileType, $mimeType, $version = 0, $public = 1) { /* {{{ */
2717        $db = $this->_dms->getDB();
2718
2719        $dir = $this->getDir();
2720
2721        $db->startTransaction();
2722        $queryStr = "INSERT INTO `tblDocumentFiles` (`comment`, `date`, `dir`, `document`, `fileType`, `mimeType`, `orgFileName`, `userID`, `name`, `version`, `public`) VALUES ".
2723            "(".$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).")";
2724        if (!$db->getResult($queryStr)) {
2725            $db->rollbackTransaction();
2726            return false;
2727        }
2728
2729        $id = $db->getInsertID('tblDocumentFiles');
2730
2731        $file = $this->getDocumentFile($id);
2732        if (is_bool($file) && !$file) {
2733            $db->rollbackTransaction();
2734            return false;
2735        }
2736
2737        if ($storage = $this->_dms->getStorage()) {
2738            $err = $storage->saveAttachment($this, $file, $tmpFile);
2739        } else {
2740        // copy file
2741        if (!SeedDMS_Core_File::makeDir($this->_dms->contentDir . $dir)) return false;
2742        if ($this->_dms->forceRename)
2743            $err = SeedDMS_Core_File::renameFile($tmpFile, $this->_dms->contentDir . $file->getPath());
2744        else
2745            $err = SeedDMS_Core_File::copyFile($tmpFile, $this->_dms->contentDir . $file->getPath());
2746        }
2747        if (!$err) {
2748            $db->rollbackTransaction();
2749            return false;
2750        }
2751
2752        $db->commitTransaction();
2753        unset($this->_documentFiles);
2754        return $file;
2755    } /* }}} */
2756
2757    public function removeDocumentFile($ID) { /* {{{ */
2758        $db = $this->_dms->getDB();
2759
2760        if (!is_numeric($ID) || $ID < 1)
2761            return false;
2762
2763        $file = $this->getDocumentFile($ID);
2764        if (is_bool($file) && !$file) return false;
2765
2766        $db->startTransaction();
2767        /* First delete the database record, because that can be undone
2768         * if deletion of the file fails.
2769         */
2770        $queryStr = "DELETE FROM `tblDocumentFiles` WHERE `document` = " . $this->getID() . " AND `id` = " . (int) $ID;
2771        if (!$db->getResult($queryStr)) {
2772            $db->rollbackTransaction();
2773            return false;
2774        }
2775
2776        if ($storage = $this->_dms->getStorage()) {
2777            if (!$storage->deleteAttachment($this, $file)) {
2778                $db->rollbackTransaction();
2779                return false;
2780            }
2781        } else {
2782        if (SeedDMS_Core_File::file_exists($this->_dms->contentDir . $file->getPath())) {
2783            if (!SeedDMS_Core_File::removeFile($this->_dms->contentDir . $file->getPath())) {
2784                $db->rollbackTransaction();
2785                return false;
2786            }
2787        }
2788        }
2789
2790        $db->commitTransaction();
2791        unset($this->_documentFiles);
2792
2793        return true;
2794    } /* }}} */
2795
2796    /**
2797     * Remove a document completly
2798     *
2799     * This methods calls the callback 'onPreRemoveDocument' before removing
2800     * the document. The current document will be passed as the second
2801     * parameter to the callback function. After successful deletion the
2802     * 'onPostRemoveDocument' callback will be used. The current document id
2803     * will be passed as the second parameter. If onPreRemoveDocument fails
2804     * the whole function will fail and the document will not be deleted.
2805     * The return value of 'onPostRemoveDocument' will be disregarded.
2806     *
2807     * @return boolean true on success, otherwise false
2808     */
2809    public function remove() { /* {{{ */
2810        $db = $this->_dms->getDB();
2811        $this->_dms->lasterror = '';
2812
2813        /* Check if 'onPreRemoveDocument' callback is set */
2814        if (isset($this->_dms->callbacks['onPreRemoveDocument'])) {
2815            foreach ($this->_dms->callbacks['onPreRemoveDocument'] as $callback) {
2816                $ret = call_user_func($callback[0], $callback[1], $this);
2817                if (is_bool($ret))
2818                    return $ret;
2819            }
2820        }
2821
2822        $res = $this->getContent();
2823        if (is_bool($res) && !$res) return false;
2824
2825        $db->startTransaction();
2826
2827        // remove content of document
2828        foreach ($this->_content as $version) {
2829            if (!$this->_removeContent($version)) {
2830                $db->rollbackTransaction();
2831                return false;
2832            }
2833        }
2834
2835        // remove all document files
2836        $res = $this->getDocumentFiles();
2837        if (is_bool($res) && !$res) {
2838            $db->rollbackTransaction();
2839            return false;
2840        }
2841
2842        foreach ($res as $documentfile)
2843            if (!$this->removeDocumentFile($documentfile->getId())) {
2844                $db->rollbackTransaction();
2845                return false;
2846            }
2847
2848        // TODO: versioning file?
2849
2850        if ($storage = $this->_dms->getStorage()) {
2851            if (!$storage->deleteDocDir($this)) {
2852                $db->rollbackTransaction();
2853                return false;
2854            }
2855        } else {
2856        if (SeedDMS_Core_File::file_exists($this->_dms->contentDir . $this->getDir()))
2857            if (!SeedDMS_Core_File::removeDir($this->_dms->contentDir . $this->getDir())) {
2858                $db->rollbackTransaction();
2859                return false;
2860            }
2861        }
2862
2863        $queryStr = "DELETE FROM `tblDocuments` WHERE `id` = " . $this->_id;
2864        if (!$db->getResult($queryStr)) {
2865            $db->rollbackTransaction();
2866            return false;
2867        }
2868        $queryStr = "DELETE FROM `tblDocumentAttributes` WHERE `document` = " . $this->_id;
2869        if (!$db->getResult($queryStr)) {
2870            $db->rollbackTransaction();
2871            return false;
2872        }
2873        $queryStr = "DELETE FROM `tblACLs` WHERE `target` = " . $this->_id . " AND `targetType` = " . T_DOCUMENT;
2874        if (!$db->getResult($queryStr)) {
2875            $db->rollbackTransaction();
2876            return false;
2877        }
2878        $queryStr = "DELETE FROM `tblDocumentLinks` WHERE `document` = " . $this->_id . " OR `target` = " . $this->_id;
2879        if (!$db->getResult($queryStr)) {
2880            $db->rollbackTransaction();
2881            return false;
2882        }
2883        $queryStr = "DELETE FROM `tblDocumentLocks` WHERE `document` = " . $this->_id;
2884        if (!$db->getResult($queryStr)) {
2885            $db->rollbackTransaction();
2886            return false;
2887        }
2888        $queryStr = "DELETE FROM `tblDocumentFiles` WHERE `document` = " . $this->_id;
2889        if (!$db->getResult($queryStr)) {
2890            $db->rollbackTransaction();
2891            return false;
2892        }
2893        $queryStr = "DELETE FROM `tblDocumentCategory` WHERE `documentID` = " . $this->_id;
2894        if (!$db->getResult($queryStr)) {
2895            $db->rollbackTransaction();
2896            return false;
2897        }
2898
2899        // Delete the notification list.
2900        $queryStr = "DELETE FROM `tblNotify` WHERE `target` = " . $this->_id . " AND `targetType` = " . T_DOCUMENT;
2901        if (!$db->getResult($queryStr)) {
2902            $db->rollbackTransaction();
2903            return false;
2904        }
2905
2906        $db->commitTransaction();
2907
2908        /* Check if 'onPostRemoveDocument' callback is set */
2909        if (isset($this->_dms->callbacks['onPostRemoveDocument'])) {
2910            foreach ($this->_dms->callbacks['onPostRemoveDocument'] as $callback) {
2911                if (!call_user_func($callback[0], $callback[1], $this)) {
2912                }
2913            }
2914        }
2915
2916        return true;
2917    } /* }}} */
2918
2919    /**
2920     * Get List of users and groups which have read access on the document.
2921     * The list will not include any guest users,
2922     * administrators and the owner of the document.
2923     *
2924     * This method is deprecated. Use
2925     * {@see SeedDMS_Core_Document::getReadAccessList()} instead.
2926     */
2927    protected function __getApproversList() { /* {{{ */
2928        return $this->getReadAccessList(0, 0, 0);
2929    } /* }}} */
2930
2931    /**
2932     * Returns a list of groups and users with read access on the document
2933     *
2934     * @param boolean $listadmin if set to true any admin will be listed too
2935     * @param boolean $listowner if set to true the owner will be listed too
2936     * @param boolean $listguest if set to true any guest will be listed too
2937     *
2938     * @return array list of users and groups
2939     */
2940    public function getReadAccessList($listadmin = 0, $listowner = 0, $listguest = 0) { /* {{{ */
2941        $db = $this->_dms->getDB();
2942
2943        $cachehash = substr(md5($listadmin.$listowner.$listguest), 0, 3);
2944        if (!isset($this->_readAccessList[$cachehash])) {
2945            $this->_readAccessList[$cachehash] = array("groups" => array(), "users" => array());
2946            $userIDs = "";
2947            $groupIDs = "";
2948            $defAccess  = $this->getDefaultAccess();
2949
2950            /* Check if the default access is < read access or >= read access.
2951             * If default access is less than read access, then create a list
2952             * of users and groups with read access.
2953             * If default access is equal or greater then read access, then
2954             * create a list of users and groups without read access.
2955             */
2956            if ($defAccess<M_READ) {
2957                // Get the list of all users and groups that are listed in the ACL as
2958                // having read access to the document.
2959                $tmpList = $this->getAccessList(M_READ, O_GTEQ);
2960            }
2961            else {
2962                // Get the list of all users and groups that DO NOT have read access
2963                // to the document.
2964                $tmpList = $this->getAccessList(M_NONE, O_LTEQ);
2965            }
2966            /** @var SeedDMS_Core_GroupAccess $groupAccess */
2967            foreach ($tmpList["groups"] as $groupAccess) {
2968                $groupIDs .= (strlen($groupIDs)==0 ? "" : ", ") . $groupAccess->getGroupID();
2969            }
2970
2971            /** @var SeedDMS_Core_UserAccess $userAccess */
2972            foreach ($tmpList["users"] as $userAccess) {
2973                $user = $userAccess->getUser();
2974//                if (!$listadmin && $user->isAdmin()) continue;
2975//                if (!$listowner && $user->getID() == $this->_ownerID) continue;
2976//                if (!$listguest && $user->isGuest()) continue;
2977                $userIDs .= (strlen($userIDs)==0 ? "" : ", ") . $user->getID();
2978            }
2979
2980            // Construct a query against the users table to identify those users
2981            // that have read access on this document, either directly through an
2982            // ACL entry, by virtue of ownership or by having administrative rights
2983            // on the database.
2984            $queryStr = "";
2985            /* If default access is less then read, $userIDs and $groupIDs contains
2986             * a list of user with read access
2987             */
2988            if ($defAccess < M_READ) {
2989                $queryStr = "SELECT DISTINCT `tblUsers`.* FROM `tblUsers` ".
2990                    "LEFT JOIN `tblGroupMembers` ON `tblGroupMembers`.`userID`=`tblUsers`.`id` ".
2991                    "WHERE 1=0".
2992                    ((strlen($groupIDs) > 0) ? " OR (`tblGroupMembers`.`groupID` IN (". $groupIDs ."))" : "").
2993                    ((strlen($userIDs) > 0) ?  " OR (`tblUsers`.`id` IN (". $userIDs ."))" : "").
2994                    " OR (`tblUsers`.`role` = ".SeedDMS_Core_User::role_admin.")".
2995                    " OR (`tblUsers`.`id` = ". $this->_ownerID . ")".
2996                    " ORDER BY `login`";
2997            }
2998            /* If default access is equal or greater than M_READ, $userIDs and
2999             * $groupIDs contains a list of user without read access
3000             * The sql statement will exclude those users and groups but include
3001             * admins and the owner
3002             */
3003            else {
3004                $queryStr = "SELECT DISTINCT `tblUsers`.* FROM `tblUsers` ".
3005                    "LEFT JOIN `tblGroupMembers` ON `tblGroupMembers`.`userID`=`tblUsers`.`id` ".
3006                    "WHERE 1=1".
3007                    (strlen($groupIDs) == 0 ? "" : " AND (`tblGroupMembers`.`groupID` NOT IN (". $groupIDs .") OR `tblGroupMembers`.`groupID` IS NULL)").
3008                    (strlen($userIDs) == 0 ? "" : " AND (`tblUsers`.`id` NOT IN (". $userIDs ."))").
3009                    " OR `tblUsers`.`id` = ". $this->_ownerID . " OR `tblUsers`.`role` = ".SeedDMS_Core_User::role_admin." ORDER BY `login` ";
3010            }
3011            $resArr = $db->getResultArray($queryStr);
3012            if (!is_bool($resArr)) {
3013                foreach ($resArr as $row) {
3014                    $user = $this->_dms->getUser($row['id']);
3015                    if (!$listadmin && $user->isAdmin()) continue;
3016                    if (!$listowner && $user->getID() == $this->_ownerID) continue;
3017                    if (!$listguest && $user->isGuest()) continue;
3018                    $this->_readAccessList[$cachehash]["users"][] = $user;
3019                }
3020            }
3021
3022            // Assemble the list of groups that have read access to the document.
3023            $queryStr = "";
3024            if ($defAccess < M_READ) {
3025                if (strlen($groupIDs)>0) {
3026                    $queryStr = "SELECT `tblGroups`.* FROM `tblGroups` ".
3027                        "WHERE `tblGroups`.`id` IN (". $groupIDs .") ORDER BY `name`";
3028                }
3029            }
3030            else {
3031                if (strlen($groupIDs)>0) {
3032                    $queryStr = "SELECT `tblGroups`.* FROM `tblGroups` ".
3033                        "WHERE `tblGroups`.`id` NOT IN (". $groupIDs .") ORDER BY `name`";
3034                }
3035                else {
3036                    $queryStr = "SELECT `tblGroups`.* FROM `tblGroups` ORDER BY `name`";
3037                }
3038            }
3039            if (strlen($queryStr)>0) {
3040                $resArr = $db->getResultArray($queryStr);
3041                if (!is_bool($resArr)) {
3042                    foreach ($resArr as $row) {
3043                        $group = $this->_dms->getGroup($row["id"]);
3044                        $this->_readAccessList[$cachehash]["groups"][] = $group;
3045                    }
3046                }
3047            }
3048        }
3049        return $this->_readAccessList[$cachehash];
3050    } /* }}} */
3051
3052    /**
3053     * Get the internally used folderList which stores the ids of folders from
3054     * the root folder to the parent folder.
3055     *
3056     * @return string column separated list of folder ids
3057     */
3058    public function getFolderList() { /* {{{ */
3059        $db = $this->_dms->getDB();
3060
3061        $queryStr = "SELECT `folderList` FROM `tblDocuments` WHERE id = ".$this->_id;
3062        $resArr = $db->getResultArray($queryStr);
3063        if (is_bool($resArr) && !$resArr)
3064            return false;
3065
3066        return $resArr[0]['folderList'];
3067    } /* }}} */
3068
3069    /**
3070     * Checks the internal data of the document and repairs it.
3071     * Currently, this function only repairs an incorrect folderList
3072     *
3073     * @return boolean true on success, otherwise false
3074     */
3075    public function repair() { /* {{{ */
3076        $db = $this->_dms->getDB();
3077
3078        $curfolderlist = $this->getFolderList();
3079
3080        // calculate the folderList of the folder
3081        $parent = $this->getFolder();
3082        $pathPrefix = "";
3083        $path = $parent->getPath();
3084        foreach ($path as $f) {
3085            $pathPrefix .= ":".$f->getID();
3086        }
3087        if (strlen($pathPrefix)>1) {
3088            $pathPrefix .= ":";
3089        }
3090        if ($curfolderlist != $pathPrefix) {
3091            $queryStr = "UPDATE `tblDocuments` SET `folderList`='".$pathPrefix."' WHERE `id` = ". $this->_id;
3092            $res = $db->getResult($queryStr);
3093            if (!$res)
3094                return false;
3095        }
3096        return true;
3097    } /* }}} */
3098
3099    /**
3100     * Calculate the disk space including all versions of the document
3101     *
3102     * This is done by using the internal database field storing the
3103     * filesize of a document version.
3104     *
3105     * @return integer total disk space in Bytes
3106     */
3107    public function getUsedDiskSpace(): int { /* {{{ */
3108        $db = $this->_dms->getDB();
3109
3110        $queryStr = "SELECT SUM(`fileSize`) sum FROM `tblDocumentContent` WHERE `document` = " . $this->_id;
3111        $resArr = $db->getResultArray($queryStr);
3112        if (is_bool($resArr) && $resArr == false)
3113            return false;
3114
3115        return (int) $resArr[0]['sum'];
3116    } /* }}} */
3117
3118    /**
3119     * Returns a list of events happend during the life of the document
3120     *
3121     * This includes the creation of new versions, approval and reviews, etc.
3122     *
3123     * @return array list of events
3124     */
3125    public function getTimeline() { /* {{{ */
3126        $db = $this->_dms->getDB();
3127
3128        $timeline = array();
3129
3130        /* No need to add entries for new version because the status log
3131         * will generate an entry as well.
3132        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = " . $this->_id;
3133        $resArr = $db->getResultArray($queryStr);
3134        if (is_bool($resArr) && $resArr == false)
3135            return false;
3136
3137        foreach ($resArr as $row) {
3138            $date = date('Y-m-d H:i:s', $row['date']);
3139            $timeline[] = array('date'=>$date, 'msg'=>'Added version '.$row['version'], 'type'=>'add_version', 'version'=>$row['version'], 'document'=>$this, 'params'=>array($row['version']));
3140        }
3141         */
3142
3143        $queryStr = "SELECT * FROM `tblDocumentFiles` WHERE `document` = " . $this->_id;
3144        $resArr = $db->getResultArray($queryStr);
3145        if (is_bool($resArr) && $resArr == false)
3146            return false;
3147
3148        foreach ($resArr as $row) {
3149            $date = date('Y-m-d H:i:s', (int) $row['date']);
3150            $timeline[] = array('date'=>$date, 'msg'=>'Added attachment "'.$row['name'].'"', 'document'=>$this, 'type'=>'add_file', 'fileid'=>$row['id']);
3151        }
3152
3153        $queryStr =
3154            "SELECT `tblDocumentStatus`.*, `tblDocumentStatusLog`.`statusLogID`,`tblDocumentStatusLog`.`status`, ".
3155            "`tblDocumentStatusLog`.`comment`, `tblDocumentStatusLog`.`date`, ".
3156            "`tblDocumentStatusLog`.`userID` ".
3157            "FROM `tblDocumentStatus` ".
3158            "LEFT JOIN `tblDocumentStatusLog` USING (`statusID`) ".
3159            "WHERE `tblDocumentStatus`.`documentID` = '". $this->_id ."' ".
3160            "ORDER BY `tblDocumentStatusLog`.`statusLogID` DESC";
3161        $resArr = $db->getResultArray($queryStr);
3162        if (is_bool($resArr) && !$resArr)
3163            return false;
3164
3165        /* The above query will also contain entries where a document status exists
3166         * but no status log entry. Those records will have no date and must be
3167         * skipped.
3168         */
3169        foreach ($resArr as $row) {
3170            if ($row['date']) {
3171                $date = $row['date'];
3172                $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']);
3173            }
3174        }
3175        return $timeline;
3176    } /* }}} */
3177
3178    /**
3179     * Transfers the document to a new user
3180     *
3181     * This method not just sets a new owner of the document but also
3182     * transfers the document links, attachments and locks to the new user.
3183     *
3184     * @return boolean true if successful, otherwise false
3185     */
3186    public function transferToUser($newuser) { /* {{{ */
3187        $db = $this->_dms->getDB();
3188
3189        if ($newuser->getId() == $this->_ownerID)
3190            return true;
3191
3192        $db->startTransaction();
3193        $queryStr = "UPDATE `tblDocuments` SET `owner` = ".$newuser->getId()." WHERE `id` = " . $this->_id;
3194        if (!$db->getResult($queryStr)) {
3195            $db->rollbackTransaction();
3196            return false;
3197        }
3198
3199        $queryStr = "UPDATE `tblDocumentLocks` SET `userID` = ".$newuser->getId()." WHERE `document` = " . $this->_id . " AND `userID` = ".$this->_ownerID;
3200        if (!$db->getResult($queryStr)) {
3201            $db->rollbackTransaction();
3202            return false;
3203        }
3204
3205        $queryStr = "UPDATE `tblDocumentLinks` SET `userID` = ".$newuser->getId()." WHERE `document` = " . $this->_id . " AND `userID` = ".$this->_ownerID;
3206        if (!$db->getResult($queryStr)) {
3207            $db->rollbackTransaction();
3208            return false;
3209        }
3210
3211        $queryStr = "UPDATE `tblDocumentFiles` SET `userID` = ".$newuser->getId()." WHERE `document` = " . $this->_id . " AND `userID` = ".$this->_ownerID;
3212        if (!$db->getResult($queryStr)) {
3213            $db->rollbackTransaction();
3214            return false;
3215        }
3216
3217        $this->_ownerID = $newuser->getID();
3218        $this->_owner = $newuser;
3219
3220        $db->commitTransaction();
3221        return true;
3222    } /* }}} */
3223
3224} /* }}} */
3225
3226
3227/**
3228 * Class to represent content of a document
3229 *
3230 * Each document has content attached to it, often called a 'version' of the
3231 * document. The document content represents a file on the disk with some
3232 * meta data stored in the database. A document content has a version number
3233 * which is incremented with each replacement of the old content. Old versions
3234 * are kept unless they are explicitly deleted by
3235 * {@see SeedDMS_Core_Document::removeContent()}.
3236 *
3237 * @category   DMS
3238 * @package    SeedDMS_Core
3239 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
3240 *             Uwe Steinmann <uwe@steinmann.cx>
3241 * @copyright  Copyright (C) 2002-2005 Markus Westphal,
3242 *             2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli,
3243 *             2010-2024 Uwe Steinmann
3244 * @version    Release: @package_version@
3245 */
3246class SeedDMS_Core_DocumentContent extends SeedDMS_Core_Object { /* {{{ */
3247    /**
3248     * @var object document
3249     */
3250    protected $_document;
3251
3252    /**
3253     * @var integer version
3254     */
3255    protected $_version;
3256
3257    /**
3258     * @var string comment
3259     */
3260    protected $_comment;
3261
3262    /**
3263     * @var string date
3264     */
3265    protected $_date;
3266
3267    /**
3268     * @var integer $_userID
3269     */
3270    protected $_userID;
3271
3272    /**
3273     * @var object $_user
3274     */
3275    protected $_user;
3276
3277    /**
3278     * @var string dir on disk (deprecated)
3279     */
3280    protected $_dir;
3281
3282    /**
3283     * @var string original file name
3284     */
3285    protected $_orgFileName;
3286
3287    /**
3288     * @var string file type (actually the extension without the leading dot)
3289     */
3290    protected $_fileType;
3291
3292    /**
3293     * @var string mime type
3294     */
3295    protected $_mimeType;
3296
3297    /**
3298     * @var string checksum of content
3299     */
3300    protected $_checksum;
3301
3302    /**
3303     * @var int size of content file
3304     */
3305    protected $_fileSize;
3306
3307    /**
3308     * @var object workflow
3309     */
3310    protected $_workflow;
3311
3312    /**
3313     * @var object workflow state
3314     */
3315    protected $_workflowState;
3316
3317    /**
3318     * @var int $_status state
3319     */
3320    protected $_status;
3321
3322    /**
3323     * @var int $_reviewStatus state
3324     */
3325    protected $_reviewStatus;
3326
3327    /**
3328     * @var int $_approvalStatus state
3329     */
3330    protected $_approvalStatus;
3331
3332    /**
3333     * @var array $_readAccessList
3334     */
3335    protected $_readAccessList;
3336
3337    /**
3338     * @var object dms
3339     */
3340    public $_dms;
3341
3342    /**
3343     * Recalculate the status of a document
3344     *
3345     * The methods checks the review and approval status and sets the
3346     * status of the document accordingly.
3347     *
3348     * If status is S_RELEASED and the version has a workflow, then set
3349     * the status to S_IN_WORKFLOW
3350     * If status is S_RELEASED and there are reviewers => set status S_DRAFT_REV
3351     * If status is S_RELEASED or S_DRAFT_REV and there are approvers => set
3352     * status S_DRAFT_APP
3353     * If status is draft and there are no approver and no reviewers => set
3354     * status to S_RELEASED
3355     * The status of a document with the current status S_OBSOLETE, S_REJECTED,
3356     * or S_EXPIRED will not be changed unless the parameter
3357     * $ignorecurrentstatus is set to true.
3358     *
3359     * This method may not be called after a negative approval or review to
3360     * recalculated the status, because
3361     * it doesn't take a defeating approval or review into account. This method
3362     * does not set the status to S_REJECTED! It will
3363     * just check for a pending workflow, approval or review and set the status
3364     * accordingly, e.g. after the list of reviewers or appovers has been
3365     * modified. If there is no pending workflow, approval or review the
3366     * status will be set to S_RELEASED.
3367     *
3368     * This method will call {@see SeedDMS_Core_DocumentContent::setStatus()}
3369     * which checks if the status has actually changed. This is, why this
3370     * function can be called at any time without harm to the status log.
3371     *
3372     * @param boolean $ignorecurrentstatus ignore the current status and
3373     *        recalculate a new status in any case
3374     * @param object $user the user initiating this method
3375     * @param string $msg message stored in status log when status is set
3376     */
3377    public function verifyStatus($ignorecurrentstatus = false, $user = null, $msg = '') { /* {{{ */
3378
3379        unset($this->_status);
3380        $st = $this->getStatus();
3381
3382        if (!$ignorecurrentstatus && ($st["status"]==S_OBSOLETE || $st["status"]==S_REJECTED || $st["status"]==S_EXPIRED )) return $st['status'];
3383
3384        $this->_workflow = null; // force to be reloaded from DB
3385        $hasworkflow =  $this->getWorkflow() ? true : false;
3386
3387        /* $pendingReview will be set when there are still open reviews */
3388        $pendingReview = false;
3389        /* $hasReview will be set if there is at least one positiv review */
3390        $hasReview = false;
3391        unset($this->_reviewStatus);  // force to be reloaded from DB
3392        $reviewStatus = $this->getReviewStatus();
3393        if (is_array($reviewStatus) && count($reviewStatus)>0) {
3394            foreach ($reviewStatus as $r) {
3395                if ($r["status"]==0) {
3396                    $pendingReview = true;
3397                    break;
3398                } elseif ($r["status"]==1) {
3399                    $hasReview = true;
3400                }
3401            }
3402        }
3403
3404        /* $pendingApproval will be set when there are still open approvals */
3405        $pendingApproval = false;
3406        /* $hasApproval will be set if there is at least one positiv review */
3407        $hasApproval = false;
3408        unset($this->_approvalStatus);  // force to be reloaded from DB
3409        $approvalStatus = $this->getApprovalStatus();
3410        if (is_array($approvalStatus) && count($approvalStatus)>0) {
3411            foreach ($approvalStatus as $a) {
3412                if ($a["status"]==0) {
3413                    $pendingApproval = true;
3414                    break;
3415                } elseif ($a["status"]==1) {
3416                    $hasApproval = true;
3417                }
3418            }
3419        }
3420
3421        /* First check for a running workflow or open reviews or approvals. */
3422        if ($hasworkflow) { $newstatus = S_IN_WORKFLOW; $ret = $this->setStatus(S_IN_WORKFLOW, $msg, $user); }
3423        elseif ($pendingReview) { $newstatus = S_DRAFT_REV; $ret = $this->setStatus(S_DRAFT_REV, $msg, $user); }
3424        elseif ($pendingApproval) { $newstatus = S_DRAFT_APP; $ret = $this->setStatus(S_DRAFT_APP, $msg, $user); }
3425        else { $newstatus = S_RELEASED; $ret = $this->setStatus(S_RELEASED, $msg, $user); }
3426        return $ret ? $newstatus : $ret;
3427    } /* }}} */
3428
3429    public function __construct($id, $document, $version, $comment, $date, $userID, $dir, $orgFileName, $fileType, $mimeType, $fileSize = 0, $checksum = '') { /* {{{ */
3430        parent::__construct($id);
3431        $this->_document = $document;
3432        $this->_version = (int) $version;
3433        $this->_comment = trim($comment);
3434        $this->_date = (int) $date;
3435        $this->_userID = (int) $userID;
3436        $this->_user = null;
3437        $this->_dir = $dir;
3438        $this->_orgFileName = trim($orgFileName);
3439        $this->_fileType = trim($fileType);
3440        $this->_mimeType = trim($mimeType);
3441        $this->_dms = $document->getDMS();
3442        if (!$fileSize) {
3443            if ($storage = $this->_dms->getStorage()) {
3444                $filesize = $storage->getContentFilesize($document, $this);
3445            } else {
3446                $this->_fileSize = SeedDMS_Core_File::fileSize($this->_dms->contentDir . $this->getPath());
3447            }
3448        } else {
3449            $this->_fileSize = (int) $fileSize;
3450        }
3451        $this->_checksum = $checksum;
3452        $this->_workflow = null;
3453        $this->_workflowState = null;
3454        $this->_readAccessList = null;
3455    } /* }}} */
3456
3457    /**
3458     * Check if this object is of type 'documentcontent'.
3459     *
3460     * @param string $type type of object
3461     */
3462    public function isType($type) { /* {{{ */
3463        return $type == 'documentcontent';
3464    } /* }}} */
3465
3466    public function getVersion() { return $this->_version; }
3467    public function getComment() { return $this->_comment; }
3468    public function getDate() { return $this->_date; }
3469    public function getOriginalFileName() { return $this->_orgFileName; }
3470    public function getFileType() { return $this->_fileType; }
3471    public function getFileName() { return $this->_version . $this->_fileType; }
3472    /**
3473     * getDir and the corresponding database table field are deprecated
3474     */
3475    private function __getDir() { return $this->_dir; }
3476    public function getMimeType() { return $this->_mimeType; }
3477    public function getDocument() { return $this->_document; }
3478
3479    public function getUser() { /* {{{ */
3480        if (!isset($this->_user))
3481            $this->_user = $this->_document->getDMS()->getUser($this->_userID);
3482        return $this->_user;
3483    } /* }}} */
3484
3485    /**
3486     * Return path of file on disk relative to the content directory
3487     *
3488     * Since version 5.1.13 a single '.' in the fileType will be skipped.
3489     * On Windows a file named 'name.' will be saved as 'name' but the fileType
3490     * will contain the a single '.'.
3491     *
3492     * @return string path of file on disc
3493     */
3494    public function getPath() { return $this->_document->getDir() . $this->_version . $this->_fileType; }
3495
3496    /*
3497     * Check if content exists in storage
3498     *
3499     * @return boolean true if file exists
3500     */
3501    public function exists() { /* {{{ */
3502        $document = $this->_document;
3503        $dms = $document->getDMS();
3504        $storage = $dms->getStorage();
3505        if($storage) {
3506            return $storage->hasContent($document, $this);
3507        } else {
3508            return file_exists($dms->contentDir . $this->getPath());
3509        }
3510        return true;
3511    } /* }}} */
3512
3513    /*
3514     * Return file size
3515     *
3516     * @return int
3517     */
3518    public function size() { /* {{{ */
3519        $document = $this->_document;
3520        $dms = $document->getDMS();
3521        $storage = $dms->getStorage();
3522        if($storage) {
3523            return $storage->getContentFilesize($document, $this);
3524        } else {
3525            return filesize($dms->contentDir . $this->getPath());
3526        }
3527        return true;
3528    } /* }}} */
3529
3530    /*
3531     * Return content of file
3532     *
3533     * @return string file content
3534     */
3535    public function content() { /* {{{ */
3536        $document = $this->_document;
3537        $dms = $document->getDMS();
3538        $storage = $dms->getStorage();
3539        if($storage) {
3540            return $storage->getContent($document, $this);
3541        } else {
3542            return file_get_contents($dms->contentDir . $this->getPath());
3543        }
3544        return true;
3545    } /* }}} */
3546
3547    /**
3548     * Set upload date of document content
3549     *
3550     * @param string $date date must be a timestamp or in the format 'Y-m-d H:i:s'
3551     *
3552     * @return boolean true on success, otherwise false
3553     */
3554    public function setDate($date = false) { /* {{{ */
3555        $db = $this->_document->getDMS()->getDB();
3556
3557        if (!$date)
3558            $date = time();
3559        else {
3560            if (is_string($date) && SeedDMS_Core_DMS::checkDate($date, 'Y-m-d H:i:s')) {
3561                $date = strtotime($date);
3562            } elseif (is_numeric($date))
3563                $date = (int) $date;
3564            else
3565                return false;
3566        }
3567
3568        $queryStr = "UPDATE `tblDocumentContent` SET `date` = ". $date." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
3569        if (!$db->getResult($queryStr))
3570            return false;
3571
3572        $this->_date = $date;
3573
3574        return true;
3575    } /* }}} */
3576
3577    public function getFileSize() { /* {{{ */
3578        return $this->_fileSize;
3579    } /* }}} */
3580
3581    /**
3582     * Set file size by reading the file
3583     */
3584    public function setFileSize() { /* {{{ */
3585        if ($storage = $this->_dms->getStorage()) {
3586            $filesize = $storage->getContentFilesize($this->_document, $this);
3587        } else {
3588            $filesize = SeedDMS_Core_File::fileSize($this->_dms->contentDir . $this->_document->getDir() . $this->getFileName());
3589        }
3590        if ($filesize === false)
3591            return false;
3592
3593        $db = $this->_document->getDMS()->getDB();
3594        $queryStr = "UPDATE `tblDocumentContent` SET `fileSize` = ".$filesize." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
3595        if (!$db->getResult($queryStr))
3596            return false;
3597        $this->_fileSize = $filesize;
3598
3599        return true;
3600    } /* }}} */
3601
3602    public function getChecksum() { /* {{{ */
3603        return $this->_checksum;
3604    } /* }}} */
3605
3606    public function getRealChecksum() { /* {{{ */
3607        if ($storage = $this->_dms->getStorage()) {
3608            $checksum = $storage->getContentChecksum($this->_document, $this);
3609        } else {
3610            $checksum = SeedDMS_Core_File::checksum($this->_dms->contentDir . $this->getPath());
3611        }
3612        return $checksum;
3613    } /* }}} */
3614
3615    /**
3616     * Set checksum by reading the file
3617     */
3618    public function setChecksum() { /* {{{ */
3619        if ($storage = $this->_dms->getStorage()) {
3620            $checksum = $storage->getContentChecksum($this->_document, $this);
3621        } else {
3622            $checksum = SeedDMS_Core_File::checksum($this->_dms->contentDir . $this->_document->getDir() . $this->getFileName());
3623        }
3624        if ($checksum === false)
3625            return false;
3626
3627        $db = $this->_document->getDMS()->getDB();
3628        $queryStr = "UPDATE `tblDocumentContent` SET `checksum` = ".$db->qstr($checksum)." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
3629        if (!$db->getResult($queryStr))
3630            return false;
3631        $this->_checksum = $checksum;
3632
3633        return true;
3634    } /* }}} */
3635
3636    public function getRealMimeType() { /* {{{ */
3637        if ($storage = $this->_dms->getStorage()) {
3638            $mimetype = $storage->getContentMimetype($this->_document, $this);
3639        } else {
3640            $mimetype = SeedDMS_Core_File::mimetype($this->_dms->contentDir . $this->getPath());
3641        }
3642        return $mimetype;
3643    } /* }}} */
3644
3645    /**
3646     * Set file type by evaluating the mime type
3647     */
3648    public function setFileType() { /* {{{ */
3649        $mimetype = $this->getMimeType();
3650
3651        $expect = SeedDMS_Core_File::fileExtension($mimetype);
3652        if ($expect && '.'.$expect != $this->_fileType) {
3653            $db = $this->_document->getDMS()->getDB();
3654            $db->startTransaction();
3655            $queryStr = "UPDATE `tblDocumentContent` SET `fileType`='.".$expect."' WHERE `id` =   ". $this->_id;
3656            $res = $db->getResult($queryStr);
3657            if ($res) {
3658                if ($storage = $this->_dms->getStorage()) {
3659                    $err = $storage->setFileType($this->_document, $this, '.'.$expect);
3660                } else {
3661                    $err = SeedDMS_Core_File::renameFile($this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType, $this->_dms->contentDir.$this->_document->getDir() . $this->_version . '.' . $expect);
3662                }
3663                if (!$err) {
3664                    $db->rollbackTransaction();
3665                } else {
3666                    $this->_fileType = '.'.$expect;
3667                    $db->commitTransaction();
3668                    return true;
3669                }
3670            } else {
3671                $db->rollbackTransaction();
3672            }
3673        }
3674
3675        return false;
3676    } /* }}} */
3677
3678    public function setMimeType($newMimetype) { /* {{{ */
3679        $db = $this->_document->getDMS()->getDB();
3680
3681        if (!$newMimetype)
3682            return false;
3683
3684        $newMimetype = trim($newMimetype);
3685
3686        if (!$newMimetype)
3687            return false;
3688
3689        $queryStr = "UPDATE `tblDocumentContent` SET `mimeType` = ".$db->qstr($newMimetype)." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
3690        if (!$db->getResult($queryStr))
3691            return false;
3692
3693        $this->_mimeType = $newMimetype;
3694
3695        return true;
3696    } /* }}} */
3697
3698    public function setComment($newComment) { /* {{{ */
3699        $db = $this->_document->getDMS()->getDB();
3700
3701        /* Check if 'onPreSetVersionComment' callback is set */
3702        if (isset($this->_dms->callbacks['onPreSetVersionComment'])) {
3703            foreach ($this->_dms->callbacks['onPreSetVersionComment'] as $callback) {
3704                $ret = call_user_func($callback[0], $callback[1], $this, $newComment);
3705                if (is_bool($ret))
3706                    return $ret;
3707            }
3708        }
3709
3710        $queryStr = "UPDATE `tblDocumentContent` SET `comment` = ".$db->qstr($newComment)." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
3711        if (!$db->getResult($queryStr))
3712            return false;
3713
3714        $oldComment = $this->_comment;
3715        $this->_comment = $newComment;
3716
3717        /* Check if 'onPostSetVersionComment' callback is set */
3718        if (isset($this->_dms->callbacks['onPostSetVersionComment'])) {
3719            foreach ($this->_dms->callbacks['onPostSetVersionComment'] as $callback) {
3720                $ret = call_user_func($callback[0], $callback[1], $this, $oldComment);
3721                if (is_bool($ret))
3722                    return $ret;
3723            }
3724        }
3725
3726        return true;
3727    } /* }}} */
3728
3729    /**
3730     * Get the latest status of the content
3731     *
3732     * The status of the content reflects its current review, approval or workflow
3733     * state. A status can be a negative or positive number or 0. A negative
3734     * numbers indicate a missing approval, review or an obsolete content.
3735     * Positive numbers indicate some kind of approval or workflow being
3736     * active, but not necessarily a release.
3737     * S_DRAFT_REV, 0
3738     * S_DRAFT_APP, 1
3739     * S_RELEASED, 2
3740     * S_IN_WORKFLOW, 3
3741     * S_REJECTED, -1
3742     * S_OBSOLETE, -2
3743     * S_EXPIRED, -3
3744     * When a content is inserted and does not need approval nor review,
3745     * then its status is set to S_RELEASED immediately. Any change of
3746     * the status is monitored in the table tblDocumentStatusLog. This
3747     * function will always return the latest entry for the content.
3748     *
3749     * @return array latest record from tblDocumentStatusLog
3750     */
3751    public function getStatus($limit = 1) { /* {{{ */
3752        $db = $this->_document->getDMS()->getDB();
3753
3754        if (!is_numeric($limit)) return false;
3755
3756        // Retrieve the current overall status of the content represented by
3757        // this object.
3758        if (!isset($this->_status)) {
3759            $queryStr =
3760                "SELECT `tblDocumentStatus`.*, `tblDocumentStatusLog`.`status`, ".
3761                "`tblDocumentStatusLog`.`comment`, `tblDocumentStatusLog`.`date`, ".
3762                "`tblDocumentStatusLog`.`userID` ".
3763                "FROM `tblDocumentStatus` ".
3764                "LEFT JOIN `tblDocumentStatusLog` USING (`statusID`) ".
3765                "WHERE `tblDocumentStatus`.`documentID` = '". $this->_document->getID() ."' ".
3766                "AND `tblDocumentStatus`.`version` = '". $this->_version ."' ".
3767                "ORDER BY `tblDocumentStatusLog`.`statusLogID` DESC LIMIT ".(int) $limit;
3768
3769            $res = $db->getResultArray($queryStr);
3770            if (is_bool($res) && !$res)
3771                return false;
3772            if (count($res)!=1)
3773                return false;
3774            $this->_status = $res[0];
3775        }
3776        return $this->_status;
3777    } /* }}} */
3778
3779    /**
3780     * Get current and former states of the document content
3781     *
3782     * @param integer $limit if not set all log entries will be returned
3783     * @return array list of status changes
3784     */
3785    public function getStatusLog($limit = 0) { /* {{{ */
3786        $db = $this->_document->getDMS()->getDB();
3787
3788        if (!is_numeric($limit)) return false;
3789
3790        $queryStr =
3791            "SELECT `tblDocumentStatus`.*, `tblDocumentStatusLog`.`status`, ".
3792            "`tblDocumentStatusLog`.`comment`, `tblDocumentStatusLog`.`date`, ".
3793            "`tblDocumentStatusLog`.`userID` ".
3794            "FROM `tblDocumentStatus` ".
3795            "LEFT JOIN `tblDocumentStatusLog` USING (`statusID`) ".
3796            "WHERE `tblDocumentStatus`.`documentID` = '". $this->_document->getID() ."' ".
3797            "AND `tblDocumentStatus`.`version` = '". $this->_version ."' ".
3798            "ORDER BY `tblDocumentStatusLog`.`statusLogID` DESC ";
3799        if ($limit)
3800            $queryStr .= "LIMIT ".(int) $limit;
3801
3802        $res = $db->getResultArray($queryStr);
3803        if (is_bool($res) && !$res)
3804            return false;
3805
3806        return $res;
3807    } /* }}} */
3808
3809    /**
3810     * Set the status of the content
3811     *
3812     * Setting the status means to add another entry into the table
3813     * tblDocumentStatusLog. The method returns also false if the status
3814     * is already set on the value passed to the method.
3815     *
3816     * @param integer $status     new status of content
3817     * @param string  $comment    comment for this status change
3818     * @param object  $updateUser user initiating the status change
3819     * @param string  $date       date in the format 'Y-m-d H:i:s'
3820     *
3821     * @return boolean true on success, otherwise false
3822     */
3823    public function setStatus(int $status, string $comment, $updateUser, $date = '') { /* {{{ */
3824        $db = $this->_document->getDMS()->getDB();
3825
3826        if (!is_numeric($status)) return false;
3827
3828        /* return an error if $updateuser is not set */
3829        if (!$updateUser || !$updateUser->isType('user'))
3830            return false;
3831
3832        // If the supplied value lies outside of the accepted range, return an
3833        // error.
3834        if ($status < S_LOWEST_STATUS || $status > S_HIGHEST_STATUS) {
3835            return false;
3836        }
3837
3838        // Retrieve the current overall status of the content represented by
3839        // this object, if it hasn't been done already.
3840        if (!isset($this->_status)) {
3841            $this->getStatus();
3842        }
3843        if ($this->_status["status"]==$status) {
3844            return true;
3845        }
3846        if ($date) {
3847            if (!SeedDMS_Core_DMS::checkDate($date, 'Y-m-d H:i:s'))
3848                return false;
3849            $ddate = $db->qstr($date);
3850        } else
3851            $ddate = $db->getCurrentDatetime();
3852        $db->startTransaction();
3853        $queryStr = "INSERT INTO `tblDocumentStatusLog` (`statusID`, `status`, `comment`, `date`, `userID`) ".
3854            "VALUES ('". $this->_status["statusID"] ."', '". (int) $status ."', ".$db->qstr($comment).", ".$ddate.", '". $updateUser->getID() ."')";
3855        $res = $db->getResult($queryStr);
3856        if (is_bool($res) && !$res) {
3857            $db->rollbackTransaction();
3858            return false;
3859        }
3860
3861        /* Check if 'onSetStatus' callback is set */
3862        if (isset($this->_dms->callbacks['onSetStatus'])) {
3863            foreach ($this->_dms->callbacks['onSetStatus'] as $callback) {
3864                $ret = call_user_func($callback[0], $callback[1], $this, $updateUser, $this->_status["status"], $status);
3865                if (is_bool($ret)) {
3866                    unset($this->_status);
3867                    if ($ret)
3868                        $db->commitTransaction();
3869                    else
3870                        $db->rollbackTransaction();
3871                    return $ret;
3872                }
3873            }
3874        }
3875
3876        $db->commitTransaction();
3877        unset($this->_status);
3878        return true;
3879    } /* }}} */
3880
3881    /**
3882     * Rewrites the complete status log
3883     *
3884     * Attention: this function is highly dangerous.
3885     * It removes an existing status log and rewrites it.
3886     * This method was added for importing an xml dump.
3887     *
3888     * @param array $statuslog new status log with the newest log entry first.
3889     * @return boolean true on success, otherwise false
3890     */
3891    public function rewriteStatusLog($statuslog) { /* {{{ */
3892        $db = $this->_document->getDMS()->getDB();
3893
3894        $queryStr = "SELECT `tblDocumentStatus`.* FROM `tblDocumentStatus` WHERE `tblDocumentStatus`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentStatus`.`version` = '". $this->_version ."' ";
3895        $res = $db->getResultArray($queryStr);
3896        if (is_bool($res) && !$res)
3897            return false;
3898
3899        $statusID = $res[0]['statusID'];
3900
3901        $db->startTransaction();
3902
3903        /* First, remove the old entries */
3904        $queryStr = "DELETE FROM `tblDocumentStatusLog` WHERE `statusID`=".$statusID;
3905        if (!$db->getResult($queryStr)) {
3906            $db->rollbackTransaction();
3907            return false;
3908        }
3909
3910        /* Second, insert the new entries */
3911        $statuslog = array_reverse($statuslog);
3912        foreach ($statuslog as $log) {
3913            if (!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
3914                $db->rollbackTransaction();
3915                return false;
3916            }
3917            $queryStr = "INSERT INTO `tblDocumentStatusLog` (`statusID`, `status`, `comment`, `date`, `userID`) ".
3918                "VALUES ('".$statusID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".$log['user']->getID().")";
3919            if (!$db->getResult($queryStr)) {
3920                $db->rollbackTransaction();
3921                return false;
3922            }
3923        }
3924
3925        $db->commitTransaction();
3926        return true;
3927    } /* }}} */
3928
3929
3930    /**
3931     * Returns the access mode similar to a document
3932     *
3933     * There is no real access mode for document content, so this is more
3934     * like a virtual access mode, derived from the status of the document
3935     * content. The function checks if {@see SeedDMS_Core_DMS::noReadForStatus}
3936     * contains the status of the version and returns M_NONE if it exists and
3937     * the user is not involved in a workflow or review/approval/revision.
3938     * This method is called by all functions that returns the content e.g.
3939     * {@see SeedDMS_Core_Document::getLatestContent()}
3940     * It is also used by {@see SeedDMS_Core_Document::getAccessMode()} to
3941     * prevent access on the whole document if there is no accessible version.
3942     *
3943     * FIXME: This method only works propperly if $u is the currently logged in
3944     * user, because noReadForStatus will be set for this user.
3945     * FIXED: instead of using $dms->noReadForStatus it is take from the user's role
3946     *
3947     * @param object $u user
3948     * @return integer either M_NONE or M_READ
3949     */
3950    public function getAccessMode($u) { /* {{{ */
3951        $dms = $this->_document->getDMS();
3952
3953        /* Check if 'onCheckAccessDocumentContent' callback is set */
3954        if (isset($this->_dms->callbacks['onCheckAccessDocumentContent'])) {
3955            foreach ($this->_dms->callbacks['onCheckAccessDocumentContent'] as $callback) {
3956                if (($ret = call_user_func($callback[0], $callback[1], $this, $u)) > 0) {
3957                    return $ret;
3958                }
3959            }
3960        }
3961
3962        return M_READ;
3963
3964        if (!$u)
3965            return M_NONE;
3966
3967        /* If read access isn't further restricted by status, than grant read access */
3968        if (!$dms->noReadForStatus)
3969            return M_READ;
3970        $noReadForStatus = $dms->noReadForStatus;
3971
3972        /* If the current status is not in list of status without read access, then grant read access */
3973        if (!in_array($this->getStatus()['status'], $noReadForStatus))
3974            return M_READ;
3975
3976        /* Administrators have unrestricted access */
3977        if ($u->isAdmin()) return M_READ;
3978
3979        /* The owner of the document has unrestricted access */
3980        $owner = $this->_document->getOwner();
3981        if ($u->getID() == $owner->getID()) return M_READ;
3982
3983        /* Read/Write access on the document will also grant access on the version */
3984        if ($this->_document->getAccessMode($u) >= M_READWRITE) return M_READ;
3985
3986        /* At this point the current status is in the list of status without read access.
3987         * The only way to still gain read access is, if the user is involved in the
3988         * process, e.g. is a reviewer, approver or an active person in the workflow.
3989         */
3990        $s = $this->getStatus();
3991        switch($s['status']) {
3992        case S_DRAFT_REV:
3993            $status = $this->getReviewStatus();
3994            foreach ($status as $r) {
3995                if ($r['status'] != -2) // Check if reviewer was removed
3996                    switch ($r["type"]) {
3997                    case 0: // Reviewer is an individual.
3998                        if ($u->getId() == $r["required"])
3999                            return M_READ;
4000                        break;
4001                    case 1: // Reviewer is a group.
4002                        $required = $dms->getGroup($r["required"]);
4003                        if (is_object($required) && $required->isMember($u))
4004                            return M_READ;
4005                        break;
4006                    }
4007            }
4008            break;
4009        case S_DRAFT_APP:
4010            $status = $this->getApprovalStatus();
4011            foreach ($status as $r) {
4012                if ($r['status'] != -2) // Check if approver was removed
4013                    switch ($r["type"]) {
4014                    case 0: // Reviewer is an individual.
4015                        if ($u->getId() == $r["required"])
4016                            return M_READ;
4017                        break;
4018                    case 1: // Reviewer is a group.
4019                        $required = $dms->getGroup($r["required"]);
4020                        if (is_object($required) && $required->isMember($u))
4021                            return M_READ;
4022                        break;
4023                    }
4024            }
4025            break;
4026        case S_RELEASED:
4027            break;
4028        case S_IN_WORKFLOW:
4029            if (!$this->_workflow)
4030                $this->getWorkflow();
4031
4032            if ($this->_workflow) {
4033                if (!$this->_workflowState)
4034                    $this->getWorkflowState();
4035                $transitions = $this->_workflow->getNextTransitions($this->_workflowState);
4036                foreach ($transitions as $transition) {
4037                    if ($this->triggerWorkflowTransitionIsAllowed($u, $transition))
4038                        return M_READ;
4039                }
4040            }
4041            break;
4042        case S_REJECTED:
4043            break;
4044        case S_OBSOLETE:
4045            break;
4046        case S_EXPIRED:
4047            break;
4048        }
4049
4050        return M_NONE;
4051    } /* }}} */
4052
4053    /**
4054     * Return a list of all reviewers separated by individuals and groups
4055     * This list will not take the review log into account. Therefore it
4056     * can contain reviewers which has actually been deleted as a reviewer.
4057     *
4058     * @return array|bool|null
4059     */
4060    public function getReviewers() { /* {{{ */
4061        $dms = $this->_document->getDMS();
4062        $db = $dms->getDB();
4063
4064        $queryStr =
4065            "SELECT * FROM `tblDocumentReviewers` WHERE `version`='".$this->_version
4066            ."' AND `documentID` = '". $this->_document->getID() ."' ";
4067
4068        $recs = $db->getResultArray($queryStr);
4069        if (is_bool($recs))
4070            return false;
4071        $reviewers = array('i'=>array(), 'g'=>array());
4072        foreach ($recs as $rec) {
4073            if ($rec['type'] == 0) {
4074                if ($u = $dms->getUser($rec['required']))
4075                    $reviewers['i'][] = $u;
4076            } elseif ($rec['type'] == 1) {
4077                if ($g = $dms->getGroup($rec['required']))
4078                    $reviewers['g'][] = $g;
4079            }
4080        }
4081        return $reviewers;
4082    } /* }}} */
4083
4084    /**
4085     * Get the current review status of the document content
4086     * The review status is a list of reviewers and its current status
4087     *
4088     * @param integer $limit the number of recent status changes per reviewer
4089     * @return array list of review status
4090     */
4091    public function getReviewStatus($limit = 1) { /* {{{ */
4092        $db = $this->_document->getDMS()->getDB();
4093
4094        if (!is_numeric($limit)) return false;
4095
4096        // Retrieve the current status of each assigned reviewer for the content
4097        // represented by this object.
4098        // FIXME: caching was turned off to make list of review log in ViewDocument
4099        // possible
4100        if (1 || !isset($this->_reviewStatus)) {
4101            /* First get a list of all reviews for this document content */
4102            $queryStr =
4103                "SELECT `reviewID` FROM `tblDocumentReviewers` WHERE `version`='".$this->_version
4104                ."' AND `documentID` = '". $this->_document->getID() ."' ";
4105            $recs = $db->getResultArray($queryStr);
4106            if (is_bool($recs) && !$recs)
4107                return false;
4108            $this->_reviewStatus = array();
4109            if ($recs) {
4110                foreach ($recs as $rec) {
4111                    $queryStr =
4112                        "SELECT `tblDocumentReviewers`.*, `tblDocumentReviewLog`.`reviewLogID`, `tblDocumentReviewLog`.`status`, ".
4113                        "`tblDocumentReviewLog`.`comment`, `tblDocumentReviewLog`.`date`, ".
4114                        "`tblDocumentReviewLog`.`userID`, `tblUsers`.`fullName`, `tblGroups`.`name` AS `groupName` ".
4115                        "FROM `tblDocumentReviewers` ".
4116                        "LEFT JOIN `tblDocumentReviewLog` USING (`reviewID`) ".
4117                        "LEFT JOIN `tblUsers` on `tblUsers`.`id` = `tblDocumentReviewers`.`required`".
4118                        "LEFT JOIN `tblGroups` on `tblGroups`.`id` = `tblDocumentReviewers`.`required`".
4119                        "WHERE `tblDocumentReviewers`.`reviewID` = '". $rec['reviewID'] ."' ".
4120                        "ORDER BY `tblDocumentReviewLog`.`reviewLogID` DESC LIMIT ".(int) $limit;
4121
4122                    $res = $db->getResultArray($queryStr);
4123                    if (is_bool($res) && !$res) {
4124                        unset($this->_reviewStatus);
4125                        return false;
4126                    }
4127                    foreach ($res as &$t) {
4128                        $filename = $this->_dms->contentDir . $this->_document->getDir().'r'.$t['reviewLogID'];
4129                        if (SeedDMS_Core_File::file_exists($filename))
4130                            $t['file'] = $filename;
4131                        else
4132                            $t['file'] = '';
4133                    }
4134                    $this->_reviewStatus = array_merge($this->_reviewStatus, $res);
4135                }
4136            }
4137        }
4138        return $this->_reviewStatus;
4139    } /* }}} */
4140
4141    /**
4142     * Get the latest entries from the review log of the document content
4143     *
4144     * @param integer $limit the number of log entries returned, defaults to 1
4145     * @return array list of review log entries
4146     */
4147    public function getReviewLog($limit = 1) { /* {{{ */
4148        $db = $this->_document->getDMS()->getDB();
4149
4150        if (!is_numeric($limit)) return false;
4151
4152        $queryStr =
4153            "SELECT * FROM `tblDocumentReviewLog` LEFT JOIN `tblDocumentReviewers` ON  `tblDocumentReviewLog`.`reviewID` = `tblDocumentReviewers`.`reviewID` WHERE `version`='".$this->_version
4154            ."' AND `documentID` = '". $this->_document->getID() ."' "
4155            ."ORDER BY `tblDocumentReviewLog`.`reviewLogID` DESC LIMIT ".(int) $limit;
4156        $recs = $db->getResultArray($queryStr);
4157        if (is_bool($recs) && !$recs)
4158            return false;
4159        return($recs);
4160    } /* }}} */
4161
4162    /**
4163     * Rewrites the complete review log
4164     *
4165     * Attention: this function is highly dangerous.
4166     * It removes an existing review log and rewrites it.
4167     * This method was added for importing an xml dump.
4168     *
4169     * @param array $reviewlog new status log with the newest log entry first.
4170     * @return boolean true on success, otherwise false
4171     */
4172    public function rewriteReviewLog($reviewers) { /* {{{ */
4173        $db = $this->_document->getDMS()->getDB();
4174
4175        $queryStr = "SELECT `tblDocumentReviewers`.* FROM `tblDocumentReviewers` WHERE `tblDocumentReviewers`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentReviewers`.`version` = '". $this->_version ."' ";
4176        $res = $db->getResultArray($queryStr);
4177        if (is_bool($res) && !$res)
4178            return false;
4179
4180        $db->startTransaction();
4181
4182        if ($res) {
4183            foreach ($res as $review) {
4184                $reviewID = $review['reviewID'];
4185
4186                /* First, remove the old entries */
4187                $queryStr = "DELETE FROM `tblDocumentReviewLog` WHERE `reviewID`=".$reviewID;
4188                if (!$db->getResult($queryStr)) {
4189                    $db->rollbackTransaction();
4190                    return false;
4191                }
4192
4193                $queryStr = "DELETE FROM `tblDocumentReviewers` WHERE `reviewID`=".$reviewID;
4194                if (!$db->getResult($queryStr)) {
4195                    $db->rollbackTransaction();
4196                    return false;
4197                }
4198            }
4199        }
4200
4201        /* Second, insert the new entries */
4202        foreach ($reviewers as $review) {
4203            $queryStr = "INSERT INTO `tblDocumentReviewers` (`documentID`, `version`, `type`, `required`) ".
4204                "VALUES ('".$this->_document->getID()."', '".$this->_version."', ".$review['type'] .", ".(is_object($review['required']) ? $review['required']->getID() : (int) $review['required']).")";
4205            if (!$db->getResult($queryStr)) {
4206                $db->rollbackTransaction();
4207                return false;
4208            }
4209            $reviewID = $db->getInsertID('tblDocumentReviewers', 'reviewID');
4210            $reviewlog = array_reverse($review['logs']);
4211            foreach ($reviewlog as $log) {
4212                if (!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
4213                    $db->rollbackTransaction();
4214                    return false;
4215                }
4216                $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
4217                    "VALUES ('".$reviewID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".(is_object($log['user']) ? $log['user']->getID() : (int) $log['user']).")";
4218                if (!$db->getResult($queryStr)) {
4219                    $db->rollbackTransaction();
4220                    return false;
4221                }
4222                $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
4223                if (!empty($log['file'])) {
4224                    SeedDMS_Core_File::copyFile($log['file'], $this->_dms->contentDir . $this->_document->getDir() . 'r' . $reviewLogID);
4225                }
4226            }
4227        }
4228
4229        $db->commitTransaction();
4230        return true;
4231    } /* }}} */
4232
4233    /**
4234     * Return a list of all approvers separated by individuals and groups
4235     * This list will not take the approval log into account. Therefore it
4236     * can contain approvers which has actually been deleted as an approver.
4237     *
4238     * @return array|bool|null
4239     */
4240    public function getApprovers() { /* {{{ */
4241        $dms = $this->_document->getDMS();
4242        $db = $dms->getDB();
4243
4244        $queryStr =
4245            "SELECT * FROM `tblDocumentApprovers` WHERE `version`='".$this->_version
4246            ."' AND `documentID` = '". $this->_document->getID() ."' ";
4247
4248        $recs = $db->getResultArray($queryStr);
4249        if (is_bool($recs))
4250            return false;
4251        $approvers = array('i'=>array(), 'g'=>array());
4252        foreach ($recs as $rec) {
4253            if ($rec['type'] == 0) {
4254                if ($u = $dms->getUser($rec['required']))
4255                    $approvers['i'][] = $u;
4256            } elseif ($rec['type'] == 1) {
4257                if ($g = $dms->getGroup($rec['required']))
4258                    $approvers['g'][] = $g;
4259            }
4260        }
4261        return $approvers;
4262    } /* }}} */
4263
4264    /**
4265     * Get the current approval status of the document content
4266     * The approval status is a list of approvers and its current status
4267     *
4268     * @param integer $limit the number of recent status changes per approver
4269     * @return array list of approval status
4270     */
4271    public function getApprovalStatus($limit = 1) { /* {{{ */
4272        $db = $this->_document->getDMS()->getDB();
4273
4274        if (!is_numeric($limit)) return false;
4275
4276        // Retrieve the current status of each assigned approver for the content
4277        // represented by this object.
4278        // FIXME: caching was turned off to make list of approval log in ViewDocument
4279        // possible
4280        if (1 || !isset($this->_approvalStatus)) {
4281            /* First get a list of all approvals for this document content */
4282            $queryStr =
4283                "SELECT `approveID` FROM `tblDocumentApprovers` WHERE `version`='".$this->_version
4284                ."' AND `documentID` = '". $this->_document->getID() ."' ";
4285            $recs = $db->getResultArray($queryStr);
4286            if (is_bool($recs) && !$recs)
4287                return false;
4288            $this->_approvalStatus = array();
4289            if ($recs) {
4290                foreach ($recs as $rec) {
4291                    $queryStr =
4292                        "SELECT `tblDocumentApprovers`.*, `tblDocumentApproveLog`.`approveLogID`, `tblDocumentApproveLog`.`status`, ".
4293                        "`tblDocumentApproveLog`.`comment`, `tblDocumentApproveLog`.`date`, ".
4294                        "`tblDocumentApproveLog`.`userID`, `tblUsers`.`fullName`, `tblGroups`.`name` AS `groupName` ".
4295                        "FROM `tblDocumentApprovers` ".
4296                        "LEFT JOIN `tblDocumentApproveLog` USING (`approveID`) ".
4297                        "LEFT JOIN `tblUsers` on `tblUsers`.`id` = `tblDocumentApprovers`.`required` ".
4298                        "LEFT JOIN `tblGroups` on `tblGroups`.`id` = `tblDocumentApprovers`.`required`".
4299                        "WHERE `tblDocumentApprovers`.`approveID` = '". $rec['approveID'] ."' ".
4300                        "ORDER BY `tblDocumentApproveLog`.`approveLogID` DESC LIMIT ".(int) $limit;
4301
4302                    $res = $db->getResultArray($queryStr);
4303                    if (is_bool($res) && !$res) {
4304                        unset($this->_approvalStatus);
4305                        return false;
4306                    }
4307                    foreach ($res as &$t) {
4308                        $filename = $this->_dms->contentDir . $this->_document->getDir().'a'.$t['approveLogID'];
4309                        if (SeedDMS_Core_File::file_exists($filename))
4310                            $t['file'] = $filename;
4311                        else
4312                            $t['file'] = '';
4313                    }
4314                    $this->_approvalStatus = array_merge($this->_approvalStatus, $res);
4315                }
4316            }
4317        }
4318        return $this->_approvalStatus;
4319    } /* }}} */
4320
4321    /**
4322     * Get the latest entries from the approval log of the document content
4323     *
4324     * @param integer $limit the number of log entries returned, defaults to 1
4325     * @return array list of approval log entries
4326     */
4327    public function getApproveLog($limit = 1) { /* {{{ */
4328        $db = $this->_document->getDMS()->getDB();
4329
4330        if (!is_numeric($limit)) return false;
4331
4332        $queryStr =
4333            "SELECT * FROM `tblDocumentApproveLog` LEFT JOIN `tblDocumentApprovers` ON  `tblDocumentApproveLog`.`approveID` = `tblDocumentApprovers`.`approveID` WHERE `version`='".$this->_version
4334            ."' AND `documentID` = '". $this->_document->getID() ."' "
4335            ."ORDER BY `tblDocumentApproveLog`.`approveLogID` DESC LIMIT ".(int) $limit;
4336        $recs = $db->getResultArray($queryStr);
4337        if (is_bool($recs) && !$recs)
4338            return false;
4339        return($recs);
4340    } /* }}} */
4341
4342    /**
4343     * Rewrites the complete approval log
4344     *
4345     * Attention: this function is highly dangerous.
4346     * It removes an existing review log and rewrites it.
4347     * This method was added for importing an xml dump.
4348     *
4349     * @param array $reviewlog new status log with the newest log entry first.
4350     * @return boolean true on success, otherwise false
4351     */
4352    public function rewriteApprovalLog($reviewers) { /* {{{ */
4353        $db = $this->_document->getDMS()->getDB();
4354
4355        $queryStr = "SELECT `tblDocumentApprovers`.* FROM `tblDocumentApprovers` WHERE `tblDocumentApprovers`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentApprovers`.`version` = '". $this->_version ."' ";
4356        $res = $db->getResultArray($queryStr);
4357        if (is_bool($res) && !$res)
4358            return false;
4359
4360        $db->startTransaction();
4361
4362        if ($res) {
4363            foreach ($res as $review) {
4364                $reviewID = $review['reviewID'];
4365
4366                /* First, remove the old entries */
4367                $queryStr = "DELETE FROM `tblDocumentApproveLog` WHERE `approveID`=".$reviewID;
4368                if (!$db->getResult($queryStr)) {
4369                    $db->rollbackTransaction();
4370                    return false;
4371                }
4372
4373                $queryStr = "DELETE FROM `tblDocumentApprovers` WHERE `approveID`=".$reviewID;
4374                if (!$db->getResult($queryStr)) {
4375                    $db->rollbackTransaction();
4376                    return false;
4377                }
4378            }
4379        }
4380
4381        /* Second, insert the new entries */
4382        foreach ($reviewers as $review) {
4383            $queryStr = "INSERT INTO `tblDocumentApprovers` (`documentID`, `version`, `type`, `required`) ".
4384                "VALUES ('".$this->_document->getID()."', '".$this->_version."', ".$review['type'] .", ".(is_object($review['required']) ? $review['required']->getID() : (int) $review['required']).")";
4385            if (!$db->getResult($queryStr)) {
4386                $db->rollbackTransaction();
4387                return false;
4388            }
4389            $reviewID = $db->getInsertID('tblDocumentApprovers', 'approveID');
4390            $reviewlog = array_reverse($review['logs']);
4391            foreach ($reviewlog as $log) {
4392                if (!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
4393                    $db->rollbackTransaction();
4394                    return false;
4395                }
4396                $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
4397                    "VALUES ('".$reviewID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".(is_object($log['user']) ? $log['user']->getID() : (int) $log['user']).")";
4398                if (!$db->getResult($queryStr)) {
4399                    $db->rollbackTransaction();
4400                    return false;
4401                }
4402                $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
4403                if (!empty($log['file'])) {
4404                    SeedDMS_Core_File::copyFile($log['file'], $this->_dms->contentDir . $this->_document->getDir() . 'a' . $approveLogID);
4405                }
4406            }
4407        }
4408
4409        $db->commitTransaction();
4410        return true;
4411    } /* }}} */
4412
4413    /**
4414     * Add user as new reviewer
4415     *
4416     * @param object $user user in charge for the review
4417     * @param object $requestUser user requesting the operation (usually the
4418     * currently logged in user)
4419     *
4420     * @return integer|false if > 0 the id of the review log, if < 0 the error
4421     * code, false in case of an sql error
4422     */
4423    public function addIndReviewer($user, $requestUser) { /* {{{ */
4424        if (!$user || !$requestUser)
4425            return -1;
4426
4427        $db = $this->_document->getDMS()->getDB();
4428
4429        if (!$user->isType('user'))
4430            return -1;
4431
4432        $userID = $user->getID();
4433
4434        // Get the list of users and groups with read access to this document.
4435        if ($this->_document->getAccessMode($user) < M_READ) {
4436            return -2;
4437        }
4438
4439        // Check to see if the user has already been added to the review list.
4440        $reviewStatus = $user->getReviewStatus($this->_document->getID(), $this->_version);
4441        if (is_bool($reviewStatus) && !$reviewStatus) {
4442            return false;
4443        }
4444        $indstatus = false;
4445        if (count($reviewStatus["indstatus"]) > 0) {
4446            $indstatus = array_pop($reviewStatus["indstatus"]);
4447            if ($indstatus["status"]!=-2) {
4448                // User is already on the list of reviewers; return an error.
4449                return -3;
4450            }
4451        }
4452
4453        // Add the user into the review database.
4454        if (!$indstatus || ($indstatus && $indstatus["status"]!=-2)) {
4455            $queryStr = "INSERT INTO `tblDocumentReviewers` (`documentID`, `version`, `type`, `required`) ".
4456                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '0', '". $userID ."')";
4457            $res = $db->getResult($queryStr);
4458            if (is_bool($res) && !$res) {
4459                return false;
4460            }
4461            $reviewID = $db->getInsertID('tblDocumentReviewers', 'reviewID');
4462        }
4463        else {
4464            $reviewID = isset($indstatus["reviewID"]) ? $indstatus["reviewID"] : null;
4465        }
4466
4467        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
4468            "VALUES ('". $reviewID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
4469        $res = $db->getResult($queryStr);
4470        if (is_bool($res) && !$res) {
4471            return false;
4472        }
4473
4474        $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
4475        $db->dropTemporaryTable('ttreviewid');
4476        return $reviewLogID;
4477    } /* }}} */
4478
4479    /**
4480     * Add group as new reviewer
4481     *
4482     * @param object $group group in charge for the review
4483     * @param object $requestUser user requesting the operation (usually the
4484     * currently logged in user)
4485     *
4486     * @return integer|false if > 0 the id of the review log, if < 0 the error
4487     * code, false in case of an sql error
4488     */
4489    public function addGrpReviewer($group, $requestUser) { /* {{{ */
4490        if (!$group || !$requestUser)
4491            return -1;
4492
4493        $db = $this->_document->getDMS()->getDB();
4494
4495        if (!$group->isType('group'))
4496            return -1;
4497
4498        $groupID = $group->getID();
4499
4500        // Get the list of users and groups with read access to this document.
4501        if (!isset($this->_readAccessList)) {
4502            // TODO: error checking.
4503            $this->_readAccessList = $this->_document->getReadAccessList();
4504        }
4505        $approved = false;
4506        foreach ($this->_readAccessList["groups"] as $appGroup) {
4507            if ($groupID == $appGroup->getID()) {
4508                $approved = true;
4509                break;
4510            }
4511        }
4512        if (!$approved) {
4513            return -2;
4514        }
4515
4516        // Check to see if the group has already been added to the review list.
4517        $reviewStatus = $group->getReviewStatus($this->_document->getID(), $this->_version);
4518        if (is_bool($reviewStatus) && !$reviewStatus) {
4519            return false;
4520        }
4521        if (count($reviewStatus) > 0 && $reviewStatus[0]["status"]!=-2) {
4522            // Group is already on the list of reviewers; return an error.
4523            return -3;
4524        }
4525
4526        // Add the group into the review database.
4527        if (!isset($reviewStatus[0]["status"]) || (isset($reviewStatus[0]["status"]) && $reviewStatus[0]["status"]!=-2)) {
4528            $queryStr = "INSERT INTO `tblDocumentReviewers` (`documentID`, `version`, `type`, `required`) ".
4529                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '1', '". $groupID ."')";
4530            $res = $db->getResult($queryStr);
4531            if (is_bool($res) && !$res) {
4532                return false;
4533            }
4534            $reviewID = $db->getInsertID('tblDocumentReviewers', 'reviewID');
4535        }
4536        else {
4537            $reviewID = isset($reviewStatus[0]["reviewID"]) ? $reviewStatus[0]["reviewID"] : null;
4538        }
4539
4540        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
4541            "VALUES ('". $reviewID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
4542        $res = $db->getResult($queryStr);
4543        if (is_bool($res) && !$res) {
4544            return false;
4545        }
4546
4547        $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
4548        $db->dropTemporaryTable('ttreviewid');
4549        return $reviewLogID;
4550    } /* }}} */
4551
4552    /**
4553     * Add a review to the document content
4554     *
4555     * This method will add an entry to the table tblDocumentReviewLog.
4556     * It will first check if the user is ment to review the document version.
4557     * It not the return value is -3.
4558     * Next it will check if the users has been removed from the list of
4559     * reviewers. In that case -4 will be returned.
4560     * If the given review status has been set by the user before, it cannot
4561     * be set again and 0 will be returned. Ð†f the review could be succesfully
4562     * added, the review log id will be returned.
4563     *
4564     * @see SeedDMS_Core_DocumentContent::setApprovalByInd()
4565     *
4566     * @param object  $user user doing the review
4567     * @param object  $requestUser user asking for the review, this is mostly
4568     * the user currently logged in.
4569     * @param integer $status status of review
4570     * @param string  $comment comment for review
4571     *
4572     * @return integer|bool new review log id, error code 0 till -4,
4573     * false in case of an sql error
4574     */
4575    public function setReviewByInd($user, $requestUser, $status, $comment, $file = '') { /* {{{ */
4576        if (!$user || !$requestUser)
4577            return -1;
4578
4579        $db = $this->_document->getDMS()->getDB();
4580
4581        if (!$user->isType('user'))
4582            return -1;
4583
4584        // Check if the user is on the review list at all.
4585        $reviewStatus = $user->getReviewStatus($this->_document->getID(), $this->_version);
4586        if (is_bool($reviewStatus) && !$reviewStatus) {
4587            return false;
4588        }
4589        if (count($reviewStatus["indstatus"])==0) {
4590            // User is not assigned to review this document. No action required.
4591            // Return an error.
4592            return -3;
4593        }
4594        $indstatus = array_pop($reviewStatus["indstatus"]);
4595        if ($indstatus["status"]==-2) {
4596            // User has been deleted from reviewers
4597            return -4;
4598        }
4599        // Check if the status is really different from the current status
4600        if ($indstatus["status"] == $status)
4601            return 0;
4602
4603        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`,
4604            `comment`, `date`, `userID`) ".
4605            "VALUES ('". $indstatus["reviewID"] ."', '".
4606            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
4607            $requestUser->getID() ."')";
4608        $res = $db->getResult($queryStr);
4609        if (is_bool($res) && !$res)
4610            return false;
4611
4612        $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
4613        if ($file) {
4614            SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'r' . $reviewLogID);
4615        }
4616        return $reviewLogID;
4617    } /* }}} */
4618
4619    /**
4620     * Add another entry to review log which resets the status
4621     *
4622     * This method will not delete anything from the database, but will add
4623     * a new review log entry which sets the status to 0. This is only allowed
4624     * if the current status is either 1 (reviewed) or -1 (rejected).
4625     *
4626     * After calling this method SeedDMS_Core_DocumentContent::verifyStatus()
4627     * should be called to recalculate the document status.
4628     *
4629     * @param integer $reviewid id of review
4630     * @param SeedDMS_Core_User $requestUser user requesting the removal
4631     * @param string $comment comment
4632     *
4633     * @return integer|bool true if successful, error code < 0,
4634     * false in case of an sql error
4635     */
4636    public function removeReview($reviewid, $requestUser, $comment = '') { /* {{{ */
4637        $db = $this->_document->getDMS()->getDB();
4638
4639        // Check to see if the user can be removed from the review list.
4640        $reviews = $this->getReviewStatus();
4641        if (is_bool($reviews) && !$reviews) {
4642            return false;
4643        }
4644        $reviewStatus = null;
4645        foreach ($reviews as $review) {
4646            if ($review['reviewID'] == $reviewid) {
4647                $reviewStatus = $review;
4648                break;
4649            }
4650        }
4651        if (!$reviewStatus)
4652            return -2;
4653
4654        // The review log entry may only be removed if the status is 1 or -1
4655        if ($reviewStatus["status"] != 1 && $reviewStatus["status"] != -1)
4656            return -3;
4657
4658        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`,
4659            `comment`, `date`, `userID`) ".
4660            "VALUES ('". $reviewStatus["reviewID"] ."', '0', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
4661            $requestUser->getID() ."')";
4662        $res = $db->getResult($queryStr);
4663        if (is_bool($res) && !$res)
4664            return false;
4665
4666        return true;
4667    } /* }}} */
4668
4669    /**
4670     * Add a review to the document content
4671     *
4672     * This method is similar to
4673     * {@see SeedDMS_Core_DocumentContent::setReviewByInd()} but adds a review
4674     * for a group instead of a user.
4675     *
4676     * @param object  $group group doing the review
4677     * @param object  $requestUser user asking for the review, this is mostly
4678     * the user currently logged in.
4679     * @param integer $status status of review
4680     * @param string  $comment comment for review
4681     *
4682     * @return integer|bool new review log id, error code 0 till -4,
4683     * false in case of an sql error
4684     */
4685    public function setReviewByGrp($group, $requestUser, $status, $comment, $file = '') { /* {{{ */
4686        if (!$group || !$requestUser)
4687            return -1;
4688
4689        $db = $this->_document->getDMS()->getDB();
4690
4691        if (!$group->isType('group'))
4692                return -1;
4693
4694        // Check if the group is on the review list at all.
4695        $reviewStatus = $group->getReviewStatus($this->_document->getID(), $this->_version);
4696        if (is_bool($reviewStatus) && !$reviewStatus) {
4697            return false;
4698        }
4699        if (count($reviewStatus)==0) {
4700            // User is not assigned to review this document. No action required.
4701            // Return an error.
4702            return -3;
4703        }
4704        if ((int) $reviewStatus[0]["status"]==-2) {
4705            // Group has been deleted from reviewers
4706            return -4;
4707        }
4708
4709        // Check if the status is really different from the current status
4710        if ($reviewStatus[0]["status"] == $status)
4711            return 0;
4712
4713        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`,
4714            `comment`, `date`, `userID`) ".
4715            "VALUES ('". $reviewStatus[0]["reviewID"] ."', '".
4716            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
4717            $requestUser->getID() ."')";
4718        $res = $db->getResult($queryStr);
4719        if (is_bool($res) && !$res)
4720            return false;
4721
4722        $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
4723        if ($file) {
4724            SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'r' . $reviewLogID);
4725        }
4726        return $reviewLogID;
4727 } /* }}} */
4728
4729    /**
4730     * Add user as new approver
4731     *
4732     * @param object $user user in charge for the approval
4733     * @param object $requestUser user requesting the operation (usually the
4734     * currently logged in user)
4735     *
4736     * @return integer|false if > 0 the id of the approval log, if < 0 the error
4737     * code, false in case of an sql error
4738     */
4739    public function addIndApprover($user, $requestUser) { /* {{{ */
4740        if (!$user || !$requestUser)
4741            return -1;
4742
4743        $db = $this->_document->getDMS()->getDB();
4744
4745        if (!$user->isType('user'))
4746            return -1;
4747
4748        $userID = $user->getID();
4749
4750        // Get the list of users and groups with read access to this document.
4751        if ($this->_document->getAccessMode($user) < M_READ) {
4752            return -2;
4753        }
4754
4755        // Check if the user has already been added to the approvers list.
4756        $approvalStatus = $user->getApprovalStatus($this->_document->getID(), $this->_version);
4757        if (is_bool($approvalStatus) && !$approvalStatus) {
4758            return false;
4759        }
4760        $indstatus = false;
4761        if (count($approvalStatus["indstatus"]) > 0) {
4762            $indstatus = array_pop($approvalStatus["indstatus"]);
4763            if ($indstatus["status"]!=-2) {
4764                // User is already on the list of approverss; return an error.
4765                return -3;
4766            }
4767        }
4768
4769        if (!$indstatus || (isset($indstatus["status"]) && $indstatus["status"]!=-2)) {
4770            // Add the user into the approvers database.
4771            $queryStr = "INSERT INTO `tblDocumentApprovers` (`documentID`, `version`, `type`, `required`) ".
4772                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '0', '". $userID ."')";
4773            $res = $db->getResult($queryStr);
4774            if (is_bool($res) && !$res) {
4775                return false;
4776            }
4777            $approveID = $db->getInsertID('tblDocumentApprovers', 'approveID');
4778        }
4779        else {
4780            $approveID = isset($indstatus["approveID"]) ? $indstatus["approveID"] : null;
4781        }
4782
4783        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
4784            "VALUES ('". $approveID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
4785        $res = $db->getResult($queryStr);
4786        if (is_bool($res) && !$res) {
4787            return false;
4788        }
4789
4790        $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
4791        $db->dropTemporaryTable('ttapproveid');
4792        return $approveLogID;
4793    } /* }}} */
4794
4795    /**
4796     * Add group as new approver
4797     *
4798     * @param object $group group in charge for the approval
4799     * @param object $requestUser user requesting the operation (usually the
4800     * currently logged in user)
4801     *
4802     * @return integer|false if > 0 the id of the approval log, if < 0 the error
4803     * code, false in case of an sql error
4804     */
4805    public function addGrpApprover($group, $requestUser) { /* {{{ */
4806        if (!$group || !$requestUser)
4807            return -1;
4808
4809        $db = $this->_document->getDMS()->getDB();
4810
4811        if (!$group->isType('group'))
4812            return -1;
4813
4814        $groupID = $group->getID();
4815
4816        // Get the list of users and groups with read access to this document.
4817        if (!isset($this->_readAccessList)) {
4818            // TODO: error checking.
4819            $this->_readAccessList = $this->_document->getReadAccessList();
4820        }
4821        $approved = false;
4822        foreach ($this->_readAccessList["groups"] as $appGroup) {
4823            if ($groupID == $appGroup->getID()) {
4824                $approved = true;
4825                break;
4826            }
4827        }
4828        if (!$approved) {
4829            return -2;
4830        }
4831
4832        // Check if the group has already been added to the approver list.
4833        $approvalStatus = $group->getApprovalStatus($this->_document->getID(), $this->_version);
4834        if (is_bool($approvalStatus) && !$approvalStatus) {
4835            return false;
4836        }
4837        if (count($approvalStatus) > 0 && $approvalStatus[0]["status"]!=-2) {
4838            // Group is already on the list of approvers; return an error.
4839            return -3;
4840        }
4841
4842        // Add the group into the approver database.
4843        if (!isset($approvalStatus[0]["status"]) || (isset($approvalStatus[0]["status"]) && $approvalStatus[0]["status"]!=-2)) {
4844            $queryStr = "INSERT INTO `tblDocumentApprovers` (`documentID`, `version`, `type`, `required`) ".
4845                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '1', '". $groupID ."')";
4846            $res = $db->getResult($queryStr);
4847            if (is_bool($res) && !$res) {
4848                return false;
4849            }
4850            $approveID = $db->getInsertID('tblDocumentApprovers', 'approveID');
4851        }
4852        else {
4853            $approveID = isset($approvalStatus[0]["approveID"]) ? $approvalStatus[0]["approveID"] :null;
4854        }
4855
4856        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
4857            "VALUES ('". $approveID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
4858        $res = $db->getResult($queryStr);
4859        if (is_bool($res) && !$res) {
4860            return false;
4861        }
4862
4863        $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
4864        $db->dropTemporaryTable('ttapproveid');
4865        return $approveLogID;
4866    } /* }}} */
4867
4868    /**
4869     * Sets approval status of a document content for a user
4870     *
4871     * This method can be used to approve or reject a document content, or
4872     * to reset its approval state. In most cases this function will be
4873     * called by an user, but  an admin may set the approval for
4874     * somebody else.
4875     * It is first checked if the user is in the list of approvers at all.
4876     * Then it is check if the approval status is already -2. In both cases
4877     * the function returns with an error.
4878     *
4879     * @see SeedDMS_Core_DocumentContent::setReviewByInd()
4880     *
4881     * @param object  $user user in charge for doing the approval
4882     * @param object  $requestUser user actually calling this function
4883     * @param integer $status the status of the approval, possible values are
4884     *        0=unprocessed (maybe used to reset a status)
4885     *        1=approved,
4886     *       -1=rejected,
4887     *       -2=user is deleted (use {link
4888     *       SeedDMS_Core_DocumentContent::delIndApprover} instead)
4889     * @param string $comment approval comment
4890     *
4891     * @return integer|bool new review log id, error code 0 till -4,
4892     * false in case of an sql error
4893     */
4894    public function setApprovalByInd($user, $requestUser, $status, $comment, $file = '') { /* {{{ */
4895        if (!$user || !$requestUser)
4896            return -1;
4897
4898        $db = $this->_document->getDMS()->getDB();
4899
4900        if (!$user->isType('user'))
4901            return -1;
4902
4903        // Check if the user is on the approval list at all.
4904        $approvalStatus = $user->getApprovalStatus($this->_document->getID(), $this->_version);
4905        if (is_bool($approvalStatus) && !$approvalStatus) {
4906            return false;
4907        }
4908        if (count($approvalStatus["indstatus"])==0) {
4909            // User is not assigned to approve this document. No action required.
4910            // Return an error.
4911            return -3;
4912        }
4913        $indstatus = array_pop($approvalStatus["indstatus"]);
4914        if ($indstatus["status"]==-2) {
4915            // User has been deleted from approvers
4916            return -4;
4917        }
4918        // Check if the status is really different from the current status
4919        if ($indstatus["status"] == $status)
4920            return 0;
4921
4922        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`,
4923            `comment`, `date`, `userID`) ".
4924            "VALUES ('". $indstatus["approveID"] ."', '".
4925            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
4926            $requestUser->getID() ."')";
4927        $res = $db->getResult($queryStr);
4928        if (is_bool($res) && !$res)
4929            return false;
4930
4931        $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
4932        if ($file) {
4933            SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'a' . $approveLogID);
4934        }
4935        return $approveLogID;
4936    } /* }}} */
4937
4938    /**
4939     * Add another entry to approval log which resets the status
4940     *
4941     * This method will not delete anything from the database, but will add
4942     * a new approval log entry which sets the status to 0. This is only allowed
4943     * if the current status is either 1 (approved) or -1 (rejected).
4944     *
4945     * After calling this method SeedDMS_Core_DocumentContent::verifyStatus()
4946     * should be called to recalculate the document status.
4947     *
4948     * @param integer $approveid id of approval
4949     * @param SeedDMS_Core_User $requestUser user requesting the removal
4950     * @param string $comment comment
4951     *
4952     * @return integer|bool true if successful, error code < 0,
4953     * false in case of an sql error
4954     */
4955    public function removeApproval($approveid, $requestUser, $comment = '') { /* {{{ */
4956        $db = $this->_document->getDMS()->getDB();
4957
4958        // Check to see if the user can be removed from the approval list.
4959        $approvals = $this->getApprovalStatus();
4960        if (is_bool($approvals) && !$approvals) {
4961            return false;
4962        }
4963        $approvalStatus = null;
4964        foreach ($approvals as $approval) {
4965            if ($approval['approveID'] == $approveid) {
4966                $approvalStatus = $approval;
4967                break;
4968            }
4969        }
4970        if (!$approvalStatus)
4971            return -2;
4972
4973        // The approval log entry may only be removed if the status is 1 or -1
4974        if ($approvalStatus["status"] != 1 && $approvalStatus["status"] != -1)
4975            return -3;
4976
4977        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`,
4978            `comment`, `date`, `userID`) ".
4979            "VALUES ('". $approvalStatus["approveID"] ."', '0', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
4980            $requestUser->getID() ."')";
4981        $res = $db->getResult($queryStr);
4982        if (is_bool($res) && !$res)
4983            return false;
4984
4985        return true;
4986    } /* }}} */
4987
4988    /**
4989     * Sets approval status of a document content for a group
4990     *
4991     * The functions behaves like
4992     * {link SeedDMS_Core_DocumentContent::setApprovalByInd} but does it for
4993     * a group instead of a user
4994     */
4995    public function setApprovalByGrp($group, $requestUser, $status, $comment, $file = '') { /* {{{ */
4996        if (!$group || !$requestUser)
4997            return -1;
4998
4999        $db = $this->_document->getDMS()->getDB();
5000
5001        if (!$group->isType('group'))
5002            return -1;
5003
5004        // Check if the group is on the approval list at all.
5005        $approvalStatus = $group->getApprovalStatus($this->_document->getID(), $this->_version);
5006        if (is_bool($approvalStatus) && !$approvalStatus) {
5007            return false;
5008        }
5009        if (count($approvalStatus)==0) {
5010            // User is not assigned to approve this document. No action required.
5011            // Return an error.
5012            return -3;
5013        }
5014        if ($approvalStatus[0]["status"]==-2) {
5015            // Group has been deleted from approvers
5016            return -4;
5017        }
5018
5019        // Check if the status is really different from the current status
5020        if ($approvalStatus[0]["status"] == $status)
5021            return 0;
5022
5023        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`,
5024            `comment`, `date`, `userID`) ".
5025            "VALUES ('". $approvalStatus[0]["approveID"] ."', '".
5026            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
5027            $requestUser->getID() ."')";
5028        $res = $db->getResult($queryStr);
5029        if (is_bool($res) && !$res)
5030            return false;
5031
5032        $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
5033        if ($file) {
5034            SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'a' . $approveLogID);
5035        }
5036        return $approveLogID;
5037    } /* }}} */
5038
5039    public function delIndReviewer($user, $requestUser, $msg = '') { /* {{{ */
5040        $db = $this->_document->getDMS()->getDB();
5041
5042        if (!$user->isType('user'))
5043            return -1;
5044
5045        // Check to see if the user can be removed from the review list.
5046        $reviewStatus = $user->getReviewStatus($this->_document->getID(), $this->_version);
5047        if (is_bool($reviewStatus) && !$reviewStatus) {
5048            return false;
5049        }
5050        if (count($reviewStatus["indstatus"])==0) {
5051            // User is not assigned to review this document. No action required.
5052            // Return an error.
5053            return -2;
5054        }
5055        $indstatus = array_pop($reviewStatus["indstatus"]);
5056        if ($indstatus["status"]!=0) {
5057            // User has already submitted a review or has already been deleted;
5058            // return an error.
5059            return -3;
5060        }
5061
5062        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
5063            "VALUES ('". $indstatus["reviewID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5064        $res = $db->getResult($queryStr);
5065        if (is_bool($res) && !$res) {
5066            return false;
5067        }
5068
5069        return 0;
5070    } /* }}} */
5071
5072    public function delGrpReviewer($group, $requestUser, $msg = '') { /* {{{ */
5073        $db = $this->_document->getDMS()->getDB();
5074
5075        if (!$group->isType('group'))
5076            return -1;
5077
5078        $groupID = $group->getID();
5079
5080        // Check to see if the user can be removed from the review list.
5081        $reviewStatus = $group->getReviewStatus($this->_document->getID(), $this->_version);
5082        if (is_bool($reviewStatus) && !$reviewStatus) {
5083            return false;
5084        }
5085        if (count($reviewStatus)==0) {
5086            // User is not assigned to review this document. No action required.
5087            // Return an error.
5088            return -2;
5089        }
5090        if ($reviewStatus[0]["status"]!=0) {
5091            // User has already submitted a review or has already been deleted;
5092            // return an error.
5093            return -3;
5094        }
5095
5096        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
5097            "VALUES ('". $reviewStatus[0]["reviewID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5098        $res = $db->getResult($queryStr);
5099        if (is_bool($res) && !$res) {
5100            return false;
5101        }
5102
5103        return 0;
5104    } /* }}} */
5105
5106    public function delIndApprover($user, $requestUser, $msg = '') { /* {{{ */
5107        $db = $this->_document->getDMS()->getDB();
5108
5109        if (!$user->isType('user'))
5110            return -1;
5111
5112        $userID = $user->getID();
5113
5114        // Check if the user is on the approval list at all.
5115        $approvalStatus = $user->getApprovalStatus($this->_document->getID(), $this->_version);
5116        if (is_bool($approvalStatus) && !$approvalStatus) {
5117            return false;
5118        }
5119        if (count($approvalStatus["indstatus"])==0) {
5120            // User is not assigned to approve this document. No action required.
5121            // Return an error.
5122            return -2;
5123        }
5124        $indstatus = array_pop($approvalStatus["indstatus"]);
5125        if ($indstatus["status"]!=0) {
5126            // User has already submitted an approval or has already been deleted;
5127            // return an error.
5128            return -3;
5129        }
5130
5131        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
5132            "VALUES ('". $indstatus["approveID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5133        $res = $db->getResult($queryStr);
5134        if (is_bool($res) && !$res) {
5135            return false;
5136        }
5137
5138        return 0;
5139    } /* }}} */
5140
5141    public function delGrpApprover($group, $requestUser, $msg = '') { /* {{{ */
5142        $db = $this->_document->getDMS()->getDB();
5143
5144        if (!$group->isType('group'))
5145            return -1;
5146
5147        $groupID = $group->getID();
5148
5149        // Check if the group is on the approval list at all.
5150        $approvalStatus = $group->getApprovalStatus($this->_document->getID(), $this->_version);
5151        if (is_bool($approvalStatus) && !$approvalStatus) {
5152            return false;
5153        }
5154        if (count($approvalStatus)==0) {
5155            // User is not assigned to approve this document. No action required.
5156            // Return an error.
5157            return -2;
5158        }
5159        if ($approvalStatus[0]["status"]!=0) {
5160            // User has already submitted an approval or has already been deleted;
5161            // return an error.
5162            return -3;
5163        }
5164
5165        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
5166            "VALUES ('". $approvalStatus[0]["approveID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5167        $res = $db->getResult($queryStr);
5168        if (is_bool($res) && !$res) {
5169            return false;
5170        }
5171
5172        return 0;
5173    } /* }}} */
5174
5175    /**
5176     * Set state of workflow assigned to the document content
5177     *
5178     * @param object $state
5179     */
5180    public function setWorkflowState($state) { /* {{{ */
5181        $db = $this->_document->getDMS()->getDB();
5182
5183        if ($this->_workflow) {
5184            $queryStr = "UPDATE `tblWorkflowDocumentContent` set `state`=". $state->getID() ." WHERE `workflow`=". intval($this->_workflow->getID()). " AND `document`=". intval($this->_document->getID()) ." AND version=". intval($this->_version) ."";
5185            if (!$db->getResult($queryStr)) {
5186                return false;
5187            }
5188            $this->_workflowState = $state;
5189            return true;
5190        }
5191        return false;
5192    } /* }}} */
5193
5194    /**
5195     * Get state of workflow assigned to the document content
5196     *
5197     * @return object/boolean an object of class SeedDMS_Core_Workflow_State
5198     *         or false in case of error, e.g. the version has not a workflow
5199     */
5200    public function getWorkflowState() { /* {{{ */
5201        $db = $this->_document->getDMS()->getDB();
5202
5203        if (!$this->_workflow)
5204            $this->getWorkflow();
5205
5206        if (!$this->_workflow)
5207            return false;
5208
5209        if (!$this->_workflowState) {
5210            $queryStr =
5211                "SELECT b.* FROM `tblWorkflowDocumentContent` a LEFT JOIN `tblWorkflowStates` b ON a.`state` = b.`id` WHERE a.`state` IS NOT NULL AND `workflow`=". intval($this->_workflow->getID())
5212                ." AND a.`version`='".$this->_version
5213                ."' AND a.`document` = '". $this->_document->getID() ."' ";
5214            $recs = $db->getResultArray($queryStr);
5215            if (!$recs)
5216                return false;
5217            $this->_workflowState = new SeedDMS_Core_Workflow_State($recs[0]['id'], $recs[0]['name'], $recs[0]['maxtime'], $recs[0]['precondfunc'], $recs[0]['documentstatus']);
5218            $this->_workflowState->setDMS($this->_document->getDMS());
5219        }
5220        return $this->_workflowState;
5221    } /* }}} */
5222
5223    /**
5224     * Assign a workflow to a document content
5225     *
5226     * @param object $workflow
5227     */
5228    public function setWorkflow($workflow, $user) { /* {{{ */
5229        $db = $this->_document->getDMS()->getDB();
5230
5231        $this->getWorkflow();
5232        if ($this->_workflow)
5233            return false;
5234
5235        if ($workflow && is_object($workflow)) {
5236            $db->startTransaction();
5237            $initstate = $workflow->getInitState();
5238            $queryStr = "INSERT INTO `tblWorkflowDocumentContent` (`workflow`, `document`, `version`, `state`, `date`) VALUES (". $workflow->getID(). ", ". $this->_document->getID() .", ". $this->_version .", ".$initstate->getID().", ".$db->getCurrentDatetime().")";
5239            if (!$db->getResult($queryStr)) {
5240                $db->rollbackTransaction();
5241                return false;
5242            }
5243            $this->_workflow = $workflow;
5244            if (!$this->setStatus(S_IN_WORKFLOW, "Added workflow '".$workflow->getName()."'", $user)) {
5245                $db->rollbackTransaction();
5246                return false;
5247            }
5248            $db->commitTransaction();
5249            return true;
5250        }
5251        return false;
5252    } /* }}} */
5253
5254    /**
5255     * Get workflow assigned to the document content
5256     *
5257     * The method returns the last workflow if one was assigned.
5258     * If the document version is in a sub workflow, it will have
5259     * a never date and therefore will be found first.
5260     *
5261     * @return object/boolean an object of class SeedDMS_Core_Workflow
5262     *         or false in case of error, e.g. the version has not a workflow
5263     */
5264    public function getWorkflow() { /* {{{ */
5265        $db = $this->_document->getDMS()->getDB();
5266
5267        if (!$this->_workflow) {
5268            $queryStr =
5269                "SELECT b.* FROM `tblWorkflowDocumentContent` a LEFT JOIN `tblWorkflows` b ON a.`workflow` = b.`id` WHERE a.`version`='".$this->_version
5270                ."' AND a.`document` = '". $this->_document->getID() ."' "
5271                ." ORDER BY `date` DESC LIMIT 1";
5272            $recs = $db->getResultArray($queryStr);
5273            if (is_bool($recs) && !$recs)
5274                return false;
5275            if (!$recs)
5276                return false;
5277            $this->_workflow = new SeedDMS_Core_Workflow($recs[0]['id'], $recs[0]['name'], $this->_document->getDMS()->getWorkflowState($recs[0]['initstate']));
5278            $this->_workflow->setDMS($this->_document->getDMS());
5279        }
5280        return $this->_workflow;
5281    } /* }}} */
5282
5283    /**
5284     * Rewrites the complete workflow log
5285     *
5286     * Attention: this function is highly dangerous.
5287     * It removes an existing workflow log and rewrites it.
5288     * This method was added for importing an xml dump.
5289     *
5290     * @param array $workflowlog new workflow log with the newest log entry first.
5291     * @return boolean true on success, otherwise false
5292     */
5293    public function rewriteWorkflowLog($workflowlog) { /* {{{ */
5294        $db = $this->_document->getDMS()->getDB();
5295
5296        $db->startTransaction();
5297
5298        /* First, remove the old entries */
5299        $queryStr = "DELETE FROM `tblWorkflowLog` WHERE `tblWorkflowLog`.`document` = '". $this->_document->getID() ."' AND `tblWorkflowLog`.`version` = '". $this->_version ."'";
5300        if (!$db->getResult($queryStr)) {
5301            $db->rollbackTransaction();
5302            return false;
5303        }
5304
5305        /* Second, insert the new entries */
5306        $workflowlog = array_reverse($workflowlog);
5307        foreach ($workflowlog as $log) {
5308            if (!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
5309                $db->rollbackTransaction();
5310                return false;
5311            }
5312            $queryStr = "INSERT INTO `tblWorkflowLog` (`document`, `version`, `workflow`, `transition`, `comment`, `date`, `userid`) ".
5313                "VALUES ('".$this->_document->getID() ."', '".(int) $this->_version."', '".(int) $log['workflow']->getID()."', '".(int) $log['transition']->getID()."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".$log['user']->getID().")";
5314            if (!$db->getResult($queryStr)) {
5315                $db->rollbackTransaction();
5316                return false;
5317            }
5318        }
5319
5320        $db->commitTransaction();
5321        return true;
5322    } /* }}} */
5323
5324    /**
5325     * Restart workflow from its initial state
5326     *
5327     * @return boolean true if workflow could be restarted
5328     *         or false in case of error
5329     */
5330    public function rewindWorkflow() { /* {{{ */
5331        $db = $this->_document->getDMS()->getDB();
5332
5333        $this->getWorkflow();
5334
5335        if (!$this->_workflow) {
5336            return true;
5337        }
5338
5339        $db->startTransaction();
5340        $queryStr = "DELETE from `tblWorkflowLog` WHERE `document` = ". $this->_document->getID() ." AND `version` = ".$this->_version." AND `workflow` = ".$this->_workflow->getID();
5341        if (!$db->getResult($queryStr)) {
5342            $db->rollbackTransaction();
5343            return false;
5344        }
5345
5346        $this->setWorkflowState($this->_workflow->getInitState());
5347        $db->commitTransaction();
5348
5349        return true;
5350    } /* }}} */
5351
5352    /**
5353     * Remove workflow
5354     *
5355     * Fully removing a workflow including entries in the workflow log is
5356     * only allowed if the workflow is still its initial state.
5357     * At a later point of time only unlinking the document from the
5358     * workflow is allowed. It will keep any log entries.
5359     * A workflow is unlinked from a document when enterNextState()
5360     * succeeds.
5361     *
5362     * @param object $user user doing initiating the removal
5363     * @param boolean $unlink if true, just unlink the workflow from the
5364     *        document but do not remove the workflow log. The $unlink
5365     *        flag has been added to detach the workflow from the document
5366     *        when it has reached a valid end state
5367     *        (see SeedDMS_Core_DocumentContent::enterNextState())
5368     * @return boolean true if workflow could be removed
5369     *         or false in case of error
5370     */
5371    public function removeWorkflow($user, $unlink = false) { /* {{{ */
5372        $db = $this->_document->getDMS()->getDB();
5373
5374        $this->getWorkflow();
5375
5376        if (!$this->_workflow) {
5377            return true;
5378        }
5379
5380        /* A workflow should always be in a state, but in case it isn't, the
5381         * at least allow to remove the workflow.
5382         */
5383        $currentstate = $this->getWorkflowState();
5384        if (!$currentstate || SeedDMS_Core_DMS::checkIfEqual($this->_workflow->getInitState(), $currentstate) || $unlink == true) {
5385            $db->startTransaction();
5386            if (!$unlink) {
5387                $queryStr =
5388                    "DELETE FROM `tblWorkflowLog` WHERE "
5389                    ."`version`='".$this->_version."' "
5390                    ." AND `document` = '". $this->_document->getID() ."' "
5391                    ." AND `workflow` = '". $this->_workflow->getID() ."' ";
5392                if (!$db->getResult($queryStr)) {
5393                    $db->rollbackTransaction();
5394                    return false;
5395                }
5396            }
5397            $queryStr =
5398                "DELETE FROM `tblWorkflowDocumentContent` WHERE "
5399                ."`version`='".$this->_version."' "
5400                ." AND `document` = '". $this->_document->getID() ."' "
5401                ." AND `workflow` = '". $this->_workflow->getID() ."' ";
5402            if (!$db->getResult($queryStr)) {
5403                $db->rollbackTransaction();
5404                return false;
5405            }
5406            $this->_workflow = null;
5407            $this->_workflowState = null;
5408            $this->verifyStatus(false, $user, 'Workflow removed');
5409            $db->commitTransaction();
5410        }
5411
5412        return true;
5413    } /* }}} */
5414
5415    /**
5416     * Run a sub workflow
5417     *
5418     * @param object $subworkflow
5419     */
5420    public function getParentWorkflow() { /* {{{ */
5421        $db = $this->_document->getDMS()->getDB();
5422
5423        /* document content must be in a workflow */
5424        $this->getWorkflow();
5425        if (!$this->_workflow)
5426            return false;
5427
5428        $queryStr =
5429            "SELECT * FROM `tblWorkflowDocumentContent` WHERE "
5430            ."`version`='".$this->_version."' "
5431            ." AND `document` = '". $this->_document->getID() ."' "
5432            ." AND `workflow` = '". $this->_workflow->getID() ."' ";
5433        $recs = $db->getResultArray($queryStr);
5434        if (is_bool($recs) && !$recs)
5435            return false;
5436        if (!$recs)
5437            return false;
5438
5439        if ($recs[0]['parentworkflow'])
5440            return $this->_document->getDMS()->getWorkflow($recs[0]['parentworkflow']);
5441
5442        return false;
5443    } /* }}} */
5444
5445    /**
5446     * Run a sub workflow
5447     *
5448     * @param object $subworkflow
5449     */
5450    public function runSubWorkflow($subworkflow) { /* {{{ */
5451        $db = $this->_document->getDMS()->getDB();
5452
5453        /* document content must be in a workflow */
5454        $this->getWorkflow();
5455        if (!$this->_workflow)
5456            return false;
5457
5458        /* The current workflow state must match the sub workflows initial state */
5459        if ($subworkflow->getInitState()->getID() != $this->_workflowState->getID())
5460            return false;
5461
5462        if ($subworkflow) {
5463            $initstate = $subworkflow->getInitState();
5464            $queryStr = "INSERT INTO `tblWorkflowDocumentContent` (`parentworkflow`, `workflow`, `document`, `version`, `state`, `date`) VALUES (". $this->_workflow->getID(). ", ". $subworkflow->getID(). ", ". $this->_document->getID() .", ". $this->_version .", ".$initstate->getID().", ".$db->getCurrentDatetime().")";
5465            if (!$db->getResult($queryStr)) {
5466                return false;
5467            }
5468            $this->_workflow = $subworkflow;
5469            return true;
5470        }
5471        return true;
5472    } /* }}} */
5473
5474    /**
5475     * Return from sub workflow to parent workflow.
5476     * The method will trigger the given transition
5477     *
5478     * FIXME: Needs much better checking if this is allowed
5479     *
5480     * @param object $user intiating the return
5481     * @param object $transtion to trigger
5482     * @param string comment for the transition trigger
5483     */
5484    public function returnFromSubWorkflow($user, $transition = null, $comment = '') { /* {{{ */
5485        $db = $this->_document->getDMS()->getDB();
5486
5487        /* document content must be in a workflow */
5488        $this->getWorkflow();
5489        if (!$this->_workflow)
5490            return false;
5491
5492        if ($this->_workflow) {
5493            $db->startTransaction();
5494
5495            $queryStr =
5496                "SELECT * FROM `tblWorkflowDocumentContent` WHERE `workflow`=". intval($this->_workflow->getID())
5497                . " AND `version`='".$this->_version
5498                ."' AND `document` = '". $this->_document->getID() ."' ";
5499            $recs = $db->getResultArray($queryStr);
5500            if (is_bool($recs) && !$recs) {
5501                $db->rollbackTransaction();
5502                return false;
5503            }
5504            if (!$recs) {
5505                $db->rollbackTransaction();
5506                return false;
5507            }
5508
5509            $queryStr = "DELETE FROM `tblWorkflowDocumentContent` WHERE `workflow` =". intval($this->_workflow->getID())." AND `document` = '". $this->_document->getID() ."' AND `version` = '" . $this->_version."'";
5510            if (!$db->getResult($queryStr)) {
5511                $db->rollbackTransaction();
5512                return false;
5513            }
5514
5515            $this->_workflow = $this->_document->getDMS()->getWorkflow($recs[0]['parentworkflow']);
5516            $this->_workflow->setDMS($this->_document->getDMS());
5517
5518            if ($transition) {
5519                if (false === $this->triggerWorkflowTransition($user, $transition, $comment)) {
5520                    $db->rollbackTransaction();
5521                    return false;
5522                }
5523            }
5524
5525            $db->commitTransaction();
5526        }
5527        return $this->_workflow;
5528    } /* }}} */
5529
5530    /**
5531     * Check if the user is allowed to trigger the transition
5532     * A user is allowed if either the user itself or
5533     * a group of which the user is a member of is registered for
5534     * triggering a transition. This method does not change the workflow
5535     * state of the document content.
5536     *
5537     * @param object $user
5538     * @return boolean true if user may trigger transaction
5539     */
5540    public function triggerWorkflowTransitionIsAllowed($user, $transition) { /* {{{ */
5541        $db = $this->_document->getDMS()->getDB();
5542
5543        if (!$this->_workflow)
5544            $this->getWorkflow();
5545
5546        if (!$this->_workflow)
5547            return false;
5548
5549        if (!$this->_workflowState)
5550            $this->getWorkflowState();
5551
5552        /* Check if the user has already triggered the transition */
5553        $queryStr =
5554            "SELECT * FROM `tblWorkflowLog` WHERE `version`='".$this->_version ."' AND `document` = '". $this->_document->getID() ."' AND `workflow` = ". $this->_workflow->getID(). " AND userid = ".$user->getID();
5555        $queryStr .= " AND `transition` = ".$transition->getID();
5556        $resArr = $db->getResultArray($queryStr);
5557        if (is_bool($resArr) && !$resArr)
5558            return false;
5559
5560        if (count($resArr))
5561            return false;
5562
5563        /* Get all transition users allowed to trigger the transition */
5564        $transusers = $transition->getUsers();
5565        if ($transusers) {
5566            foreach ($transusers as $transuser) {
5567                if ($user->getID() == $transuser->getUser()->getID())
5568                    return true;
5569            }
5570        }
5571
5572        /* Get all transition groups whose members are allowed to trigger
5573         * the transition */
5574        $transgroups = $transition->getGroups();
5575        if ($transgroups) {
5576            foreach ($transgroups as $transgroup) {
5577                $group = $transgroup->getGroup();
5578                if ($group->isMember($user))
5579                    return true;
5580            }
5581        }
5582
5583        return false;
5584    } /* }}} */
5585
5586    /**
5587     * Check if all conditions are met to change the workflow state
5588     * of a document content (run the transition).
5589     * The conditions are met if all explicitly set users and a sufficient
5590     * number of users of the groups have acknowledged the content.
5591     *
5592     * @return boolean true if transaction maybe executed
5593     */
5594    public function executeWorkflowTransitionIsAllowed($transition) { /* {{{ */
5595        if (!$this->_workflow)
5596            $this->getWorkflow();
5597
5598        if (!$this->_workflow)
5599            return false;
5600
5601        if (!$this->_workflowState)
5602            $this->getWorkflowState();
5603
5604        /* Get the Log of transition triggers */
5605        $entries = $this->getWorkflowLog($transition);
5606        if (!$entries)
5607            return false;
5608
5609        /* Get all transition users allowed to trigger the transition
5610         * $allowedusers is a list of all users allowed to trigger the
5611         * transition
5612         */
5613        $transusers = $transition->getUsers();
5614        $allowedusers = array();
5615        foreach ($transusers as $transuser) {
5616            $a = $transuser->getUser();
5617            $allowedusers[$a->getID()] = $a;
5618        }
5619
5620        /* Get all transition groups whose members are allowed to trigger
5621         * the transition */
5622        $transgroups = $transition->getGroups();
5623        foreach ($entries as $entry) {
5624            $loguser = $entry->getUser();
5625            /* Unset each allowed user if it was found in the log */
5626            if (isset($allowedusers[$loguser->getID()]))
5627                unset($allowedusers[$loguser->getID()]);
5628            /* Also check groups if required. Count the group membership of
5629             * each user in the log in the array $gg
5630             */
5631            if ($transgroups) {
5632                $loggroups = $loguser->getGroups();
5633                foreach ($loggroups as $loggroup) {
5634                    if (!isset($gg[$loggroup->getID()]))
5635                        $gg[$loggroup->getID()] = 1;
5636                    else
5637                        $gg[$loggroup->getID()]++;
5638                }
5639            }
5640        }
5641        /* If there are allowed users left, then there some users still
5642         * need to trigger the transition.
5643         */
5644        if ($allowedusers)
5645            return false;
5646
5647        if ($transgroups) {
5648            foreach ($transgroups as $transgroup) {
5649                $group = $transgroup->getGroup();
5650                $minusers = $transgroup->getNumOfUsers();
5651                if (!isset($gg[$group->getID()]))
5652                    return false;
5653                if ($gg[$group->getID()] < $minusers)
5654                    return false;
5655            }
5656        }
5657        return true;
5658    } /* }}} */
5659
5660    /**
5661     * Trigger transition
5662     *
5663     * This method will be deprecated
5664     *
5665     * The method will first check if the user is allowed to trigger the
5666     * transition. If the user is allowed, an entry in the workflow log
5667     * will be added, which is later used to check if the transition
5668     * can actually be processed. The method will finally call
5669     * executeWorkflowTransitionIsAllowed() which checks all log entries
5670     * and does the transitions post function if all users and groups have
5671     * triggered the transition. Finally enterNextState() is called which
5672     * will try to enter the next state.
5673     *
5674     * @param object $user
5675     * @param object $transition
5676     * @param string $comment user comment
5677     * @return boolean/object next state if transition could be triggered and
5678     *         then next state could be entered,
5679     *         true if the transition could just be triggered or
5680     *         false in case of an error
5681     */
5682    public function triggerWorkflowTransition($user, $transition, $comment = '') { /* {{{ */
5683        $db = $this->_document->getDMS()->getDB();
5684
5685        if (!$this->_workflow)
5686            $this->getWorkflow();
5687
5688        if (!$this->_workflow)
5689            return false;
5690
5691        if (!$this->_workflowState)
5692            $this->getWorkflowState();
5693
5694        if (!$this->_workflowState)
5695            return false;
5696
5697        /* Check if the user is allowed to trigger the transition.
5698         */
5699        if (!$this->triggerWorkflowTransitionIsAllowed($user, $transition))
5700            return false;
5701
5702        $state = $this->_workflowState;
5703        $queryStr = "INSERT INTO `tblWorkflowLog` (`document`, `version`, `workflow`, `userid`, `transition`, `date`, `comment`) VALUES (".$this->_document->getID().", ".$this->_version.", " . (int) $this->_workflow->getID() . ", " .(int) $user->getID(). ", ".(int) $transition->getID().", ".$db->getCurrentDatetime().", ".$db->qstr($comment).")";
5704        if (!$db->getResult($queryStr))
5705            return false;
5706
5707        /* Check if this transition is processed. Run the post function in
5708         * that case. A transition is processed when all users and groups
5709         * have triggered it.
5710         */
5711        if ($this->executeWorkflowTransitionIsAllowed($transition)) {
5712            /* run post function of transition */
5713//            echo "run post function of transition ".$transition->getID()."<br />";
5714        }
5715
5716        /* Go into the next state. This will only succeed if the pre condition
5717         * function of that states succeeds.
5718         */
5719        $nextstate = $transition->getNextState();
5720        if ($this->enterNextState($user, $nextstate)) {
5721            return $nextstate;
5722        }
5723        return true;
5724
5725    } /* }}} */
5726
5727    /**
5728     * Enter next state of workflow if possible
5729     *
5730     * The method will check if one of the following states in the workflow
5731     * can be reached.
5732     * It does it by running
5733     * the precondition function of that state. The precondition function
5734     * gets a list of all transitions leading to the state. It will
5735     * determine, whether the transitions has been triggered and if that
5736     * is sufficient to enter the next state. If no pre condition function
5737     * is set, then 1 of n transtions are enough to enter the next state.
5738     *
5739     * If moving in the next state is possible and this state has a
5740     * corresponding document state, then the document state will be
5741     * updated and the workflow will be detached from the document.
5742     *
5743     * @param object $user
5744     * @param object $nextstate
5745     * @return boolean true if the state could be reached
5746     *         false if not
5747     */
5748    public function enterNextState($user, $nextstate) { /* {{{ */
5749
5750            /* run the pre condition of the next state. If it is not set
5751             * the next state will be reached if one of the transitions
5752             * leading to the given state can be processed.
5753             */
5754            if ($nextstate->getPreCondFunc() == '') {
5755                $transitions = $this->_workflow->getPreviousTransitions($nextstate);
5756                foreach ($transitions as $transition) {
5757//                echo "transition ".$transition->getID()." led to state ".$nextstate->getName()."<br />";
5758                    if ($this->executeWorkflowTransitionIsAllowed($transition)) {
5759//                    echo "stepping into next state<br />";
5760                        $this->setWorkflowState($nextstate);
5761
5762                        /* Check if the new workflow state has a mapping into a
5763                         * document state. If yes, set the document state will
5764                         * be updated and the workflow will be removed from the
5765                         * document.
5766                         */
5767                        $docstate = $nextstate->getDocumentStatus();
5768                        if ($docstate == S_RELEASED || $docstate == S_REJECTED) {
5769                            $this->setStatus($docstate, "Workflow has ended", $user);
5770                            /* Detach the workflow from the document, but keep the
5771                             * workflow log
5772                             */
5773                            $this->removeWorkflow($user, true);
5774                            return true ;
5775                        }
5776
5777                        /* make sure the users and groups allowed to trigger the next
5778                         * transitions are also allowed to read the document
5779                         */
5780                        $transitions = $this->_workflow->getNextTransitions($nextstate);
5781                        foreach ($transitions as $tran) {
5782//                            echo "checking access for users/groups allowed to trigger transition ".$tran->getID()."<br />";
5783                            $transusers = $tran->getUsers();
5784                            foreach ($transusers as $transuser) {
5785                                $u = $transuser->getUser();
5786//                                echo $u->getFullName()."<br />";
5787                                if ($this->_document->getAccessMode($u) < M_READ) {
5788                                    $this->_document->addAccess(M_READ, $u->getID(), 1);
5789//                                    echo "granted read access<br />";
5790                                } else {
5791//                                    echo "has already access<br />";
5792                                }
5793                            }
5794                            $transgroups = $tran->getGroups();
5795                            foreach ($transgroups as $transgroup) {
5796                                $g = $transgroup->getGroup();
5797//                                echo $g->getName()."<br />";
5798                                if ($this->_document->getGroupAccessMode($g) < M_READ) {
5799                                    $this->_document->addAccess(M_READ, $g->getID(), 0);
5800//                                    echo "granted read access<br />";
5801                                } else {
5802//                                    echo "has already access<br />";
5803                                }
5804                            }
5805                        }
5806                        return true;
5807                    } else {
5808//                        echo "transition not ready for process now<br />";
5809                    }
5810                }
5811                return false;
5812            } else {
5813                return false;
5814            }
5815
5816    } /* }}} */
5817
5818    /**
5819     * Get the so far logged operations on the document content within the
5820     * workflow. Even after finishing the workflow (when the document content
5821     * does not have workflow set anymore) this function returns the list of all
5822     * log entries.
5823     *
5824     * @return array list of objects
5825     */
5826    public function getWorkflowLog($transition = null) { /* {{{ */
5827        $db = $this->_document->getDMS()->getDB();
5828
5829/*
5830        if (!$this->_workflow)
5831            $this->getWorkflow();
5832
5833        if (!$this->_workflow)
5834            return false;
5835*/
5836        $queryStr =
5837            "SELECT * FROM `tblWorkflowLog` WHERE `version`='".$this->_version ."' AND `document` = '". $this->_document->getID() ."'"; // AND `workflow` = ". $this->_workflow->getID();
5838        if ($transition)
5839            $queryStr .= " AND `transition` = ".$transition->getID();
5840        $queryStr .= " ORDER BY `date`";
5841        $resArr = $db->getResultArray($queryStr);
5842        if (is_bool($resArr) && !$resArr)
5843            return false;
5844
5845        $workflowlogs = array();
5846        for ($i = 0; $i < count($resArr); $i++) {
5847            $workflow = $this->_document->getDMS()->getWorkflow($resArr[$i]["workflow"]);
5848            $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"]);
5849            $workflowlog->setDMS($this);
5850            $workflowlogs[$i] = $workflowlog;
5851        }
5852
5853        return $workflowlogs;
5854    } /* }}} */
5855
5856    /**
5857     * Get the latest workflow log entry for the document content within the
5858     * workflow. Even after finishing the workflow (when the document content
5859     * does not have workflow set anymore) this function returns the last
5860     * log entry.
5861     *
5862     * @return object
5863     */
5864    public function getLastWorkflowLog() { /* {{{ */
5865        $db = $this->_document->getDMS()->getDB();
5866
5867/*
5868        if (!$this->_workflow)
5869            $this->getWorkflow();
5870
5871        if (!$this->_workflow)
5872            return false;
5873 */
5874        $queryStr =
5875            "SELECT * FROM `tblWorkflowLog` WHERE `version`='".$this->_version ."' AND `document` = '". $this->_document->getID() ."'"; // AND `workflow` = ". $this->_workflow->getID();
5876        $queryStr .= " ORDER BY `id` DESC LIMIT 1";
5877        $resArr = $db->getResultArray($queryStr);
5878        if (is_bool($resArr) && !$resArr)
5879            return false;
5880
5881        $i = 0;
5882        $workflow = $this->_document->getDMS()->getWorkflow($resArr[$i]["workflow"]);
5883        $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"]);
5884        $workflowlog->setDMS($this);
5885
5886        return $workflowlog;
5887    } /* }}} */
5888
5889    /**
5890     * Check if the document content needs an action by a user
5891     *
5892     * This method will return true if document content is in a transition
5893     * which can be triggered by the given user.
5894     *
5895     * @param SeedDMS_Core_User $user
5896     * @return boolean true is action is needed
5897     */
5898    public function needsWorkflowAction($user) { /* {{{ */
5899        $needwkflaction = false;
5900        if ($this->_workflow) {
5901            if (!$this->_workflowState)
5902                $this->getWorkflowState();
5903            $workflowstate = $this->_workflowState;
5904            if ($transitions = $this->_workflow->getNextTransitions($workflowstate)) {
5905                foreach ($transitions as $transition) {
5906                    if ($this->triggerWorkflowTransitionIsAllowed($user, $transition)) {
5907                        $needwkflaction = true;
5908                    }
5909                }
5910            }
5911        }
5912        return $needwkflaction;
5913    } /* }}} */
5914
5915    /**
5916     * Checks the internal data of the document version and repairs it.
5917     * Currently, this function only repairs a missing filetype
5918     *
5919     * @return boolean true on success, otherwise false
5920     */
5921    public function repair() { /* {{{ */
5922        $dms = $this->_document->getDMS();
5923        $db = $this->_dms->getDB();
5924
5925        if (SeedDMS_Core_File::file_exists($this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType)) {
5926            if (strlen($this->_fileType) < 2) {
5927                switch($this->_mimeType) {
5928                case "application/pdf":
5929                case "image/png":
5930                case "image/gif":
5931                case "image/jpg":
5932                    $expect = substr($this->_mimeType, -3, 3);
5933                    if ($this->_fileType != '.'.$expect) {
5934                        $db->startTransaction();
5935                        $queryStr = "UPDATE `tblDocumentContent` SET `fileType`='.".$expect."' WHERE `id` = ". $this->_id;
5936                        $res = $db->getResult($queryStr);
5937                        if ($res) {
5938                            if (!SeedDMS_Core_File::renameFile($this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType, $this->_dms->contentDir.$this->_document->getDir() . $this->_version . '.' . $expect)) {
5939                                $db->rollbackTransaction();
5940                            } else {
5941                                $db->commitTransaction();
5942                            }
5943                        } else {
5944                            $db->rollbackTransaction();
5945                        }
5946                    }
5947                    break;
5948                }
5949            }
5950        } elseif (SeedDMS_Core_File::file_exists($this->_document->getDir() . $this->_version . '.')) {
5951            echo "no file";
5952        } else {
5953            echo $this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType;
5954        }
5955        return true;
5956    } /* }}} */
5957
5958} /* }}} */
5959
5960
5961/**
5962 * Class to represent a link between two document
5963 *
5964 * Document links are to establish a reference from one document to
5965 * another document. The owner of the document link may not be the same
5966 * as the owner of one of the documents.
5967 * Use {@see SeedDMS_Core_Document::addDocumentLink()} to add a reference
5968 * to another document.
5969 *
5970 * @category   DMS
5971 * @package    SeedDMS_Core
5972 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
5973 *             Uwe Steinmann <uwe@steinmann.cx>
5974 * @copyright  Copyright (C) 2002-2005 Markus Westphal,
5975 *             2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli,
5976 *             2010-2024 Uwe Steinmann
5977 * @version    Release: @package_version@
5978 */
5979class SeedDMS_Core_DocumentLink { /* {{{ */
5980    /**
5981     * @var integer internal id of document link
5982     */
5983    protected $_id;
5984
5985    /**
5986     * @var SeedDMS_Core_Document reference to document this link belongs to
5987     */
5988    protected $_document;
5989
5990    /**
5991     * @var object reference to target document this link points to
5992     */
5993    protected $_target;
5994
5995    /**
5996     * @var integer id of user who is the owner of this link
5997     */
5998    protected $_userID;
5999
6000    /**
6001     * @var object $_user user who is the owner of this link
6002     */
6003    protected $_user;
6004
6005    /**
6006     * @var integer 1 if this link is public, or 0 if is only visible to the owner
6007     */
6008    protected $_public;
6009
6010    /**
6011     * SeedDMS_Core_DocumentLink constructor.
6012     * @param $id
6013     * @param $document
6014     * @param $target
6015     * @param $userID
6016     * @param $public
6017     */
6018    public function __construct($id, $document, $target, $userID, $public) {
6019        $this->_id = $id;
6020        $this->_document = $document;
6021        $this->_target = $target;
6022        $this->_userID = $userID;
6023        $this->_user = null;
6024        $this->_public = $public ? true : false;
6025    }
6026
6027    /**
6028     * Check if this object is of type 'documentlink'.
6029     *
6030     * @param string $type type of object
6031     */
6032    public function isType($type) { /* {{{ */
6033        return $type == 'documentlink';
6034    } /* }}} */
6035
6036    /**
6037     * @return int
6038     */
6039    public function getID() { return $this->_id; }
6040
6041    /**
6042     * @return SeedDMS_Core_Document
6043     */
6044    public function getDocument() {
6045        return $this->_document;
6046    }
6047
6048    /**
6049     * @return object
6050     */
6051    public function getTarget() {
6052        return $this->_target;
6053    }
6054
6055    /**
6056     * @return bool|SeedDMS_Core_User
6057     */
6058    public function getUser() {
6059        if (!isset($this->_user)) {
6060            $this->_user = $this->_document->getDMS()->getUser($this->_userID);
6061        }
6062        return $this->_user;
6063    }
6064
6065    /**
6066     * @return int
6067     */
6068    public function isPublic() { return $this->_public; }
6069
6070    /**
6071     * Returns the access mode similar to a document
6072     *
6073     * There is no real access mode for document links, so this is just
6074     * another way to add more access restrictions than the default restrictions.
6075     * It is only called for public document links, not accessed by the owner
6076     * or the administrator.
6077     *
6078     * @param SeedDMS_Core_User $u user
6079     * @param $source
6080     * @param $target
6081     * @return int either M_NONE or M_READ
6082     */
6083    public function getAccessMode($u, $source, $target) { /* {{{ */
6084        $dms = $this->_document->getDMS();
6085
6086        /* Check if 'onCheckAccessDocumentLink' callback is set */
6087        if (isset($dms->callbacks['onCheckAccessDocumentLink'])) {
6088            foreach ($dms->callbacks['onCheckAccessDocumentLink'] as $callback) {
6089                if (($ret = call_user_func($callback[0], $callback[1], $this, $u, $source, $target)) > 0) {
6090                    return $ret;
6091                }
6092            }
6093        }
6094
6095        return M_READ;
6096    } /* }}} */
6097
6098} /* }}} */
6099
6100/**
6101 * Class to represent a file attached to a document
6102 *
6103 * Beside the regular document content arbitrary files can be attached
6104 * to a document. This is a similar concept as attaching files to emails.
6105 * The owner of the attached file and the document may not be the same.
6106 * Use {@see SeedDMS_Core_Document::addDocumentFile()} to attach a file.
6107 *
6108 * @category   DMS
6109 * @package    SeedDMS_Core
6110 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
6111 *             Uwe Steinmann <uwe@steinmann.cx>
6112 * @copyright  Copyright (C) 2002-2005 Markus Westphal,
6113 *             2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli,
6114 *             2010-2024 Uwe Steinmann
6115 * @version    Release: @package_version@
6116 */
6117class SeedDMS_Core_DocumentFile { /* {{{ */
6118    /**
6119     * @var integer internal id of document file
6120     */
6121    protected $_id;
6122
6123    /**
6124     * @var SeedDMS_Core_Document reference to document this file belongs to
6125     */
6126    protected $_document;
6127
6128    /**
6129     * @var integer id of user who is the owner of this link
6130     */
6131    protected $_userID;
6132
6133    /**
6134     * @var object user who is the owner of this link
6135     */
6136    protected $_user;
6137
6138    /**
6139     * @var string comment for the attached file
6140     */
6141    protected $_comment;
6142
6143    /**
6144     * @var string date when the file was attached
6145     */
6146    protected $_date;
6147
6148    /**
6149     * @var integer version of document this file is attached to
6150     */
6151    protected $_version;
6152
6153    /**
6154     * @var integer 1 if this link is public, or 0 if is only visible to the owner
6155     */
6156    protected $_public;
6157
6158    /**
6159     * @var string directory where the file is stored. This is the
6160     * document id with a proceding '/'.
6161     * FIXME: looks like this isn't used anymore. The file path is
6162     * constructed by getPath()
6163     */
6164    protected $_dir;
6165
6166    /**
6167     * @var string extension of the original file name with a leading '.'
6168     */
6169    protected $_fileType;
6170
6171    /**
6172     * @var string mime type of the file
6173     */
6174    protected $_mimeType;
6175
6176    /**
6177     * @var string name of the file that was originally uploaded
6178     */
6179    protected $_orgFileName;
6180
6181    /**
6182     * @var string name of the file as given by the user
6183     */
6184    protected $_name;
6185
6186    /**
6187     * SeedDMS_Core_DocumentFile constructor.
6188     * @param $id
6189     * @param $document
6190     * @param $userID
6191     * @param $comment
6192     * @param $date
6193     * @param $dir
6194     * @param $fileType
6195     * @param $mimeType
6196     * @param $orgFileName
6197     * @param $name
6198     * @param $version
6199     * @param $public
6200     */
6201    public function __construct($id, $document, $userID, $comment, $date, $dir, $fileType, $mimeType, $orgFileName, $name, $version, $public) {
6202        $this->_id = $id;
6203        $this->_document = $document;
6204        $this->_userID = $userID;
6205        $this->_user = null;
6206        $this->_comment = trim($comment);
6207        $this->_date = $date;
6208        $this->_dir = $dir;
6209        $this->_fileType = trim($fileType);
6210        $this->_mimeType = trim($mimeType);
6211        $this->_orgFileName = trim($orgFileName);
6212        $this->_name = trim($name);
6213        $this->_version = $version;
6214        $this->_public = $public ? true : false;
6215    }
6216
6217    /**
6218     * Check if this object is of type 'documentfile'.
6219     *
6220     * @param string $type type of object
6221     */
6222    public function isType($type) { /* {{{ */
6223        return $type == 'documentfile';
6224    } /* }}} */
6225
6226    /**
6227     * @return int
6228     */
6229    public function getID() { return $this->_id; }
6230
6231    /**
6232     * @return SeedDMS_Core_Document
6233     */
6234    public function getDocument() { return $this->_document; }
6235
6236    /**
6237     * @return int
6238     */
6239    public function getUserID() { return $this->_userID; }
6240
6241    /**
6242     * @return string
6243     */
6244    public function getComment() { return $this->_comment; }
6245
6246    /*
6247     * Set the comment of the document file
6248     *
6249     * @param string $newComment string new comment of document
6250     */
6251    public function setComment($newComment) { /* {{{ */
6252        $db = $this->_document->getDMS()->getDB();
6253
6254        $queryStr = "UPDATE `tblDocumentFiles` SET `comment` = ".$db->qstr($newComment)." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id;
6255        if (!$db->getResult($queryStr))
6256            return false;
6257
6258        $this->_comment = $newComment;
6259        return true;
6260    } /* }}} */
6261
6262    /**
6263     * @return string
6264     */
6265    public function getDate() { return $this->_date; }
6266
6267    /**
6268     * Set creation date of the document file
6269     *
6270     * @param integer $date timestamp of creation date. If false then set it
6271     * to the current timestamp
6272     * @return boolean true on success
6273     */
6274    public function setDate($date = null) { /* {{{ */
6275        $db = $this->_document->getDMS()->getDB();
6276
6277        if (!$date)
6278            $date = time();
6279        else {
6280            if (!is_numeric($date))
6281                return false;
6282        }
6283
6284        $queryStr = "UPDATE `tblDocumentFiles` SET `date` = " . (int) $date . " WHERE `id` = ". $this->_id;
6285        if (!$db->getResult($queryStr))
6286            return false;
6287        $this->_date = $date;
6288        return true;
6289    } /* }}} */
6290
6291    /**
6292     * @return string
6293     */
6294    public function getDir() { return $this->_dir; }
6295
6296    /**
6297     * @return string
6298     */
6299    public function getFileType() { return $this->_fileType; }
6300
6301    /**
6302     * @return string
6303     */
6304    public function getMimeType() { return $this->_mimeType; }
6305
6306    public function getRealMimeType() { /* {{{ */
6307        $dms = $this->_document->getDMS();
6308        if ($storage = $dms->getStorage()) {
6309            $mimetype = $storage->getAttachmentMimetype($this->_document, $this);
6310        } else {
6311            $mimetype = SeedDMS_Core_File::mimetype($dms->contentDir . $this->getPath());
6312        }
6313        return $mimetype;
6314    } /* }}} */
6315
6316    /**
6317     * @return string
6318     */
6319    public function getOriginalFileName() { return $this->_orgFileName; }
6320
6321    /**
6322     * @return string
6323     */
6324    public function getName() { return $this->_name; }
6325
6326    /*
6327     * Set the name of the document file
6328     *
6329     * @param $newComment string new name of document
6330     */
6331    public function setName($newName) { /* {{{ */
6332        $db = $this->_document->getDMS()->getDB();
6333
6334        $queryStr = "UPDATE `tblDocumentFiles` SET `name` = ".$db->qstr($newName)." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id;
6335        if (!$db->getResult($queryStr))
6336            return false;
6337
6338        $this->_name = $newName;
6339
6340        return true;
6341    } /* }}} */
6342
6343    /**
6344     * @return bool|SeedDMS_Core_User
6345     */
6346    public function getUser() {
6347        if (!isset($this->_user))
6348            $this->_user = $this->_document->getDMS()->getUser($this->_userID);
6349        return $this->_user;
6350    }
6351
6352    /**
6353     * @return string
6354     */
6355    public function getPath() {
6356        return $this->_document->getDir() . "f" .$this->_id . $this->_fileType;
6357    }
6358
6359    /*
6360     * Check if file exists in storage
6361     *
6362     * @return boolean true if file exists
6363     */
6364    public function exists() { /* {{{ */
6365        $document = $this->_document;
6366        $dms = $document->getDMS();
6367        $storage = $dms->getStorage();
6368        if($storage) {
6369            return $storage->hasAttachment($document, $this);
6370        } else {
6371            return file_exists($dms->contentDir . $this->getPath());
6372        }
6373        return true;
6374    } /* }}} */
6375
6376    /*
6377     * Return size of file
6378     *
6379     * @return boolean true if file exists
6380     */
6381    public function size() { /* {{{ */
6382        $document = $this->_document;
6383        $dms = $document->getDMS();
6384        $storage = $dms->getStorage();
6385        if($storage) {
6386            return $storage->getAttachmentFilesize($document, $this);
6387        } else {
6388            return filesize($dms->contentDir . $this->getPath());
6389        }
6390        return true;
6391    } /* }}} */
6392
6393    /*
6394     * Return content of file
6395     *
6396     * @return sting file content
6397     */
6398    public function content() { /* {{{ */
6399        $document = $this->_document;
6400        $dms = $document->getDMS();
6401        $storage = $dms->getStorage();
6402        if($storage) {
6403            return $storage->getAttachment($document, $this);
6404        } else {
6405            return file_get_contents($dms->contentDir . $this->getPath());
6406        }
6407        return true;
6408    } /* }}} */
6409
6410    /**
6411     * @return int
6412     */
6413    public function getVersion() { return $this->_version; }
6414
6415    /*
6416     * Set the version of the document file
6417     *
6418     * @param $newComment string new version of document
6419     */
6420    public function setVersion($newVersion) { /* {{{ */
6421        $db = $this->_document->getDMS()->getDB();
6422
6423        if (!is_numeric($newVersion) && $newVersion != '')
6424            return false;
6425
6426        $queryStr = "UPDATE `tblDocumentFiles` SET `version` = ".(int) $newVersion." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id;
6427        if (!$db->getResult($queryStr))
6428            return false;
6429
6430        $this->_version = (int) $newVersion;
6431        return true;
6432    } /* }}} */
6433
6434    /**
6435     * @return int
6436     */
6437    public function isPublic() { return $this->_public; }
6438
6439    /*
6440     * Set the public flag of the document file
6441     *
6442     * @param $newComment string new comment of document
6443     */
6444    public function setPublic($newPublic) { /* {{{ */
6445        $db = $this->_document->getDMS()->getDB();
6446
6447        $queryStr = "UPDATE `tblDocumentFiles` SET `public` = ".($newPublic ? 1 : 0)." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id;
6448        if (!$db->getResult($queryStr))
6449            return false;
6450
6451        $this->_public = $newPublic ? true : false;
6452        return true;
6453    } /* }}} */
6454
6455    /**
6456     * Returns the access mode similar to a document
6457     *
6458     * There is no real access mode for document files, so this is just
6459     * another way to add more access restrictions than the default restrictions.
6460     * It is only called for public document files, not accessed by the owner
6461     * or the administrator.
6462     *
6463     * @param object $u user
6464     * @return integer either M_NONE or M_READ
6465     */
6466    public function getAccessMode($u) { /* {{{ */
6467        $dms = $this->_document->getDMS();
6468
6469        /* Check if 'onCheckAccessDocumentLink' callback is set */
6470        if (isset($this->_dms->callbacks['onCheckAccessDocumentFile'])) {
6471            foreach ($this->_dms->callbacks['onCheckAccessDocumentFile'] as $callback) {
6472                if (($ret = call_user_func($callback[0], $callback[1], $this, $u)) > 0) {
6473                    return $ret;
6474                }
6475            }
6476        }
6477
6478        return M_READ;
6479    } /* }}} */
6480
6481} /* }}} */
6482
6483//
6484// Perhaps not the cleanest object ever devised, it exists to encapsulate all
6485// of the data generated during the addition of new content to the database.
6486// The object stores a copy of the new DocumentContent object, the newly assigned
6487// reviewers and approvers and the status.
6488//
6489/**
6490 * Class to represent a list of document contents
6491 *
6492 * @category   DMS
6493 * @package    SeedDMS_Core
6494 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
6495 *             Uwe Steinmann <uwe@steinmann.cx>
6496 * @copyright  Copyright (C) 2002-2005 Markus Westphal,
6497 *             2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli,
6498 *             2010-2024 Uwe Steinmann
6499 * @version    Release: @package_version@
6500 */
6501class SeedDMS_Core_AddContentResultSet { /* {{{ */
6502
6503    /**
6504     * @var null
6505     */
6506    protected $_indReviewers;
6507
6508    /**
6509     * @var null
6510     */
6511    protected $_grpReviewers;
6512
6513    /**
6514     * @var null
6515     */
6516    protected $_indApprovers;
6517
6518    /**
6519     * @var null
6520     */
6521    protected $_grpApprovers;
6522
6523    /**
6524     * @var
6525     */
6526    protected $_content;
6527
6528    /**
6529     * @var null
6530     */
6531    protected $_status;
6532
6533    /**
6534     * @var SeedDMS_Core_DMS back reference to document management system
6535     */
6536    protected $_dms;
6537
6538    /**
6539     * SeedDMS_Core_AddContentResultSet constructor.
6540     * @param $content
6541     */
6542    public function __construct($content) { /* {{{ */
6543        $this->_content = $content;
6544        $this->_indReviewers = null;
6545        $this->_grpReviewers = null;
6546        $this->_indApprovers = null;
6547        $this->_grpApprovers = null;
6548        $this->_status = null;
6549        $this->_dms = null;
6550    } /* }}} */
6551
6552    /**
6553     * Set dms this object belongs to.
6554     *
6555     * Each object needs a reference to the dms it belongs to. It will be
6556     * set when the object is created.
6557     * The dms has a references to the currently logged in user
6558     * and the database connection.
6559     *
6560     * @param SeedDMS_Core_DMS $dms reference to dms
6561     */
6562    public function setDMS($dms) { /* {{{ */
6563        $this->_dms = $dms;
6564    } /* }}} */
6565
6566    /**
6567     * @param $reviewer
6568     * @param $type
6569     * @param $status
6570     * @return bool
6571     */
6572    public function addReviewer($reviewer, $type, $status) { /* {{{ */
6573        $dms = $this->_dms;
6574
6575        if (!is_object($reviewer) || (strcasecmp($type, "i") && strcasecmp($type, "g")) && !is_integer($status)) {
6576            return false;
6577        }
6578        if (!strcasecmp($type, "i")) {
6579            if (strcasecmp(get_class($reviewer), $dms->getClassname("user"))) {
6580                return false;
6581            }
6582            if ($this->_indReviewers == null) {
6583                $this->_indReviewers = array();
6584            }
6585            $this->_indReviewers[$status][] = $reviewer;
6586        }
6587        if (!strcasecmp($type, "g")) {
6588            if (strcasecmp(get_class($reviewer), $dms->getClassname("group"))) {
6589                return false;
6590            }
6591            if ($this->_grpReviewers == null) {
6592                $this->_grpReviewers = array();
6593            }
6594            $this->_grpReviewers[$status][] = $reviewer;
6595        }
6596        return true;
6597    } /* }}} */
6598
6599    /**
6600     * @param $approver
6601     * @param $type
6602     * @param $status
6603     * @return bool
6604     */
6605    public function addApprover($approver, $type, $status) { /* {{{ */
6606        $dms = $this->_dms;
6607
6608        if (!is_object($approver) || (strcasecmp($type, "i") && strcasecmp($type, "g")) && !is_integer($status)) {
6609            return false;
6610        }
6611        if (!strcasecmp($type, "i")) {
6612            if (strcasecmp(get_class($approver), $dms->getClassname("user"))) {
6613                return false;
6614            }
6615            if ($this->_indApprovers == null) {
6616                $this->_indApprovers = array();
6617            }
6618            $this->_indApprovers[$status][] = $approver;
6619        }
6620        if (!strcasecmp($type, "g")) {
6621            if (strcasecmp(get_class($approver), $dms->getClassname("group"))) {
6622                return false;
6623            }
6624            if ($this->_grpApprovers == null) {
6625                $this->_grpApprovers = array();
6626            }
6627            $this->_grpApprovers[$status][] = $approver;
6628        }
6629        return true;
6630    } /* }}} */
6631
6632    /**
6633     * @param $status
6634     * @return bool
6635     */
6636    public function setStatus($status) { /* {{{ */
6637        if (!is_integer($status)) {
6638            return false;
6639        }
6640        if ($status<-3 || $status>3) {
6641            return false;
6642        }
6643        $this->_status = $status;
6644        return true;
6645    } /* }}} */
6646
6647    /**
6648     * @return null
6649     */
6650    public function getStatus() { /* {{{ */
6651        return $this->_status;
6652    } /* }}} */
6653
6654    /**
6655     * @return mixed
6656     */
6657    public function getContent() { /* {{{ */
6658        return $this->_content;
6659    } /* }}} */
6660
6661    /**
6662     * @param $type
6663     * @return array|bool|null
6664     */
6665    public function getReviewers($type) { /* {{{ */
6666        if (strcasecmp($type, "i") && strcasecmp($type, "g")) {
6667            return false;
6668        }
6669        if (!strcasecmp($type, "i")) {
6670            return ($this->_indReviewers == null ? array() : $this->_indReviewers);
6671        }
6672        else {
6673            return ($this->_grpReviewers == null ? array() : $this->_grpReviewers);
6674        }
6675    } /* }}} */
6676
6677    /**
6678     * @param $type
6679     * @return array|bool|null
6680     */
6681    public function getApprovers($type) { /* {{{ */
6682        if (strcasecmp($type, "i") && strcasecmp($type, "g")) {
6683            return false;
6684        }
6685        if (!strcasecmp($type, "i")) {
6686            return ($this->_indApprovers == null ? array() : $this->_indApprovers);
6687        }
6688        else {
6689            return ($this->_grpApprovers == null ? array() : $this->_grpApprovers);
6690        }
6691    } /* }}} */
6692} /* }}} */