Completed
Push — master ( b4e5c2...6db701 )
by Daniel
03:09
created

DMSDocument::removeActionPanelTask()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
1
<?php
2
3
/**
4
 * @package dms
5
 *
6
 * @property Varchar Filename
7
 * @property Varchar Folder
8
 * @property Varchar Title
9
 * @property Text Description
10
 * @property int ViewCount
11
 * @property Boolean EmbargoedIndefinitely
12
 * @property Boolean EmbargoedUntilPublished
13
 * @property DateTime EmbargoedUntilDate
14
 * @property DateTime ExpireAtDate
15
 * @property Enum DownloadBehavior
16
 * @property Enum CanViewType Enum('Anyone, LoggedInUsers, OnlyTheseUsers', 'Anyone')
17
 * @property Enum CanEditType Enum('LoggedInUsers, OnlyTheseUsers', 'LoggedInUsers')
18
 *
19
 * @method ManyManyList RelatedDocuments
20
 * @method ManyManyList ViewerGroups
21
 * @method ManyManyList EditorGroups
22
 *
23
 * @method Member CreatedBy
24
 * @property Int CreatedByID
25
 * @method Member LastEditedBy
26
 * @property Int LastEditedByID
27
 *
28
 */
29
class DMSDocument extends DataObject implements DMSDocumentInterface
0 ignored issues
show
Coding Style Compatibility introduced by helpfulrobot
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
30
{
31
    private static $db = array(
0 ignored issues
show
Comprehensibility introduced by helpfulrobot
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by helpfulrobot
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
32
        "Filename" => "Varchar(255)", // eg. 3469~2011-energysaving-report.pdf
33
        "Folder" => "Varchar(255)",    // eg.	0
34
        "Title" => 'Varchar(1024)', // eg. "Energy Saving Report for Year 2011, New Zealand LandCorp"
35
        "Description" => 'Text',
36
        "ViewCount" => 'Int',
37
        "EmbargoedIndefinitely" => 'Boolean(false)',
38
        "EmbargoedUntilPublished" => 'Boolean(false)',
39
        "EmbargoedUntilDate" => 'SS_DateTime',
40
        "ExpireAtDate" => 'SS_DateTime',
41
        "DownloadBehavior" => 'Enum(array("open","download"), "download")',
42
        "CanViewType" => "Enum('Anyone, LoggedInUsers, OnlyTheseUsers', 'Anyone')",
43
        "CanEditType" => "Enum('LoggedInUsers, OnlyTheseUsers', 'LoggedInUsers')",
44
    );
45
46
    private static $belongs_many_many = array(
0 ignored issues
show
Comprehensibility introduced by helpfulrobot
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by helpfulrobot
The property $belongs_many_many is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
47
        'Sets' => 'DMSDocumentSet'
48
    );
49
50
    private static $has_one = array(
0 ignored issues
show
Comprehensibility introduced by Robbie Averill
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by Franco Springveldt
The property $has_one is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
51
        'CoverImage' => 'Image',
52
        'CreatedBy' => 'Member',
53
        'LastEditedBy' => 'Member',
54
    );
55
56
    private static $many_many = array(
0 ignored issues
show
Comprehensibility introduced by helpfulrobot
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by helpfulrobot
The property $many_many is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
57
        'RelatedDocuments' => 'DMSDocument',
58
        'ViewerGroups' => 'Group',
59
        'EditorGroups' => 'Group',
60
    );
61
62
    private static $display_fields = array(
0 ignored issues
show
Unused Code introduced by helpfulrobot
The property $display_fields is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
63
        'ID' => 'ID',
64
        'Title' => 'Title',
65
        'FilenameWithoutID' => 'Filename',
66
        'LastEdited' => 'Last Edited'
67
    );
68
69
    private static $singular_name = 'Document';
0 ignored issues
show
Comprehensibility introduced by helpfulrobot
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by helpfulrobot
The property $singular_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
70
71
    private static $plural_name = 'Documents';
0 ignored issues
show
Comprehensibility introduced by helpfulrobot
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by helpfulrobot
The property $plural_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
72
73
    private static $summary_fields = array(
0 ignored issues
show
Comprehensibility introduced by helpfulrobot
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by Sacha Judd
The property $summary_fields is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
74
        'Filename' => 'Filename',
75
        'Title' => 'Title',
76
        'getRelatedPages.count' => 'Page Use',
77
        'ViewCount' => 'ViewCount',
78
    );
79
80
    /**
81
     * @var string download|open
82
     * @config
83
     */
84
    private static $default_download_behaviour = 'download';
0 ignored issues
show
Unused Code introduced by James Barnsley
The property $default_download_behaviour is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
85
86
    /**
87
     * A key value map of the "actions" tabs that will be added to the CMS fields
88
     *
89
     * @var array
90
     */
91
    protected $actionTasks = array(
92
        'embargo' => 'Embargo',
93
        'expiry' => 'Expiry',
94
        'replace' => 'Replace',
95
        'find-usage' => 'Usage',
96
        'find-references' => 'References',
97
        'find-relateddocuments' => 'Related Documents',
98
        'permissions' => 'Permissions'
99
    );
100
101
    public function canView($member = null)
102
    {
103 View Code Duplication
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
0 ignored issues
show
Duplication introduced by helpfulrobot
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
104
            $member = Member::currentUser();
105
        }
106
107
        // extended access checks
108
        $results = $this->extend('canView', $member);
109
110
        if ($results && is_array($results)) {
0 ignored issues
show
Bug Best Practice introduced by helpfulrobot
The expression $results of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
111
            if (!min($results)) {
112
                return false;
113
            }
114
        }
115
116
        if (!$this->CanViewType || $this->CanViewType == 'Anyone') {
117
            return true;
118
        }
119 View Code Duplication
        if ($member && Permission::checkMember($member, array(
0 ignored issues
show
Duplication introduced by Franco Springveldt
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
120
                'ADMIN',
121
                'SITETREE_EDIT_ALL',
122
                'SITETREE_VIEW_ALL',
123
            ))
124
        ) {
125
            return true;
126
        }
127
128
        if ($this->isHidden()) {
129
            return false;
130
        }
131
132
        if ($this->CanViewType == 'LoggedInUsers') {
133
            return $member && $member->exists();
134
        }
135
136
        if ($this->CanViewType == 'OnlyTheseUsers' && $this->ViewerGroups()->count()) {
0 ignored issues
show
Documentation Bug introduced by Franco Springveldt
The method ViewerGroups does not exist on object<DMSDocument>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
137
            return ($member && $member->inGroups($this->ViewerGroups()));
0 ignored issues
show
Documentation Bug introduced by Franco Springveldt
The method ViewerGroups does not exist on object<DMSDocument>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
138
        }
139
140
        return $this->canEdit($member);
141
    }
142
143
    public function canEdit($member = null)
144
    {
145 View Code Duplication
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
0 ignored issues
show
Duplication introduced by helpfulrobot
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
146
            $member = Member::currentUser();
147
        }
148
149
        $results = $this->extend('canEdit', $member);
150
151
        if ($results && is_array($results)) {
0 ignored issues
show
Bug Best Practice introduced by helpfulrobot
The expression $results of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
152
            if (!min($results)) {
153
                return false;
154
            }
155
        }
156
157
        // Do early admin check
158 View Code Duplication
        if ($member && Permission::checkMember(
0 ignored issues
show
Duplication introduced by Franco Springveldt
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
159
            $member,
160
            array(
161
                    'ADMIN',
162
                    'SITETREE_EDIT_ALL',
163
                    'SITETREE_VIEW_ALL',
164
                )
165
        )
166
        ) {
167
            return true;
168
        }
169
170
        if ($this->CanEditType === 'LoggedInUsers') {
171
            return $member && $member->exists();
172
        }
173
174
        if ($this->CanEditType === 'OnlyTheseUsers' && $this->EditorGroups()->count()) {
0 ignored issues
show
Documentation Bug introduced by Franco Springveldt
The method EditorGroups does not exist on object<DMSDocument>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
175
            return $member && $member->inGroups($this->EditorGroups());
0 ignored issues
show
Documentation Bug introduced by Franco Springveldt
The method EditorGroups does not exist on object<DMSDocument>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
176
        }
177
178
        return ($member && Permission::checkMember($member, array('ADMIN', 'SITETREE_EDIT_ALL')));
179
    }
180
181
    /**
182
     * @param Member $member
0 ignored issues
show
Documentation introduced by helpfulrobot
Should the type for parameter $member not be Member|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
183
     *
184
     * @return boolean
185
     */
186
    public function canCreate($member = null)
187
    {
188 View Code Duplication
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
0 ignored issues
show
Duplication introduced by helpfulrobot
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
189
            $member = Member::currentUser();
190
        }
191
192
        $results = $this->extend('canCreate', $member);
193
194
        if ($results && is_array($results)) {
0 ignored issues
show
Bug Best Practice introduced by helpfulrobot
The expression $results of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
195
            if (!min($results)) {
196
                return false;
197
            }
198
        }
199
200
        // Do early admin check
201
        if ($member &&
202
            Permission::checkMember($member, array('CMS_ACCESS_DMSDocumentAdmin'))
203
        ) {
204
            return true;
205
        }
206
207
        return $this->canEdit($member);
208
    }
209
210
    /**
211
     * @param Member $member
0 ignored issues
show
Documentation introduced by helpfulrobot
Should the type for parameter $member not be Member|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
212
     *
213
     * @return boolean
214
     */
215
    public function canDelete($member = null)
216
    {
217 View Code Duplication
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
0 ignored issues
show
Duplication introduced by helpfulrobot
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
218
            $member = Member::currentUser();
219
        }
220
221
        $results = $this->extend('canDelete', $member);
222
223
        if ($results && is_array($results)) {
0 ignored issues
show
Bug Best Practice introduced by helpfulrobot
The expression $results of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
224
            if (!min($results)) {
225
                return false;
226
            }
227
        }
228
229
        return $this->canEdit($member);
230
    }
231
232
    /**
233
     * Increase ViewCount by 1, without update any other record fields such as
234
     * LastEdited.
235
     *
236
     * @return DMSDocument
237
     */
238
    public function trackView()
239
    {
240
        if ($this->ID > 0) {
241
            $count = $this->ViewCount + 1;
242
243
            $this->ViewCount = $count;
244
245
            DB::query("UPDATE \"DMSDocument\" SET \"ViewCount\"='$count' WHERE \"ID\"={$this->ID}");
246
        }
247
248
        return $this;
249
    }
250
251
    /**
252
     * Returns a link to download this document from the DMS store.
253
     * Alternatively a basic javascript alert will be shown should the user not have view permissions. An extension
254
     * point for this was also added.
255
     *
256
     * To extend use the following from within an Extension subclass:
257
     *
258
     * <code>
259
     * public function updateGetLink($result)
260
     * {
261
     *     // Do something here
262
     * }
263
     * </code>
264
     *
265
     * @return string
266
     */
267
    public function getLink()
268
    {
269
        $urlSegment = sprintf('%d-%s', $this->ID, URLSegmentFilter::create()->filter($this->getTitle()));
270
        $result = Controller::join_links(Director::baseURL(), 'dmsdocument/' . $urlSegment);
271
        if (!$this->canView()) {
272
            $result = sprintf("javascript:alert('%s')", $this->getPermissionDeniedReason());
273
        }
274
275
        $this->extend('updateGetLink', $result);
276
277
        return $result;
278
    }
279
280
    /**
281
     * @return string
282
     */
283
    public function Link()
284
    {
285
        return $this->getLink();
286
    }
287
288
    /**
289
     * Hides the document, so it does not show up when getByPage($myPage) is
290
     * called (without specifying the $showEmbargoed = true parameter).
291
     *
292
     * This is similar to expire, except that this method should be used to hide
293
     * documents that have not yet gone live.
294
     *
295
     * @param bool $write Save change to the database
296
     *
297
     * @return DMSDocument
298
     */
299
    public function embargoIndefinitely($write = true)
300
    {
301
        $this->EmbargoedIndefinitely = true;
302
303
        if ($write) {
304
            $this->write();
305
        }
306
307
        return $this;
308
    }
309
310
    /**
311
     * Hides the document until any page it is linked to is published
312
     *
313
     * @param bool $write Save change to database
314
     *
315
     * @return DMSDocument
316
     */
317
    public function embargoUntilPublished($write = true)
318
    {
319
        $this->EmbargoedUntilPublished = true;
320
321
        if ($write) {
322
            $this->write();
323
        }
324
325
        return $this;
326
    }
327
328
    /**
329
     * Returns if this is Document is embargoed or expired.
330
     *
331
     * Also, returns if the document should be displayed on the front-end,
332
     * respecting the current reading mode of the site and the embargo status.
333
     *
334
     * I.e. if a document is embargoed until published, then it should still
335
     * show up in draft mode.
336
     *
337
     * @return bool
338
     */
339
    public function isHidden()
340
    {
341
        $hidden = $this->isEmbargoed() || $this->isExpired();
342
        $readingMode = Versioned::get_reading_mode();
343
344
        if ($readingMode == "Stage.Stage") {
345
            if ($this->EmbargoedUntilPublished == true) {
0 ignored issues
show
Coding Style Best Practice introduced by helpfulrobot
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
346
                $hidden = false;
347
            }
348
        }
349
350
        return $hidden;
351
    }
352
353
    /**
354
     * Returns if this is Document is embargoed.
355
     *
356
     * @return bool
357
     */
358
    public function isEmbargoed()
359
    {
360
        if (is_object($this->EmbargoedUntilDate)) {
361
            $this->EmbargoedUntilDate = $this->EmbargoedUntilDate->Value;
0 ignored issues
show
Bug introduced by helpfulrobot
The property Value does not seem to exist in DateTime.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
362
        }
363
364
        $embargoed = false;
365
366
        if ($this->EmbargoedIndefinitely) {
367
            $embargoed = true;
368
        } elseif ($this->EmbargoedUntilPublished) {
369
            $embargoed = true;
370
        } elseif (!empty($this->EmbargoedUntilDate)) {
371
            if (SS_Datetime::now()->Value < $this->EmbargoedUntilDate) {
372
                $embargoed = true;
373
            }
374
        }
375
376
        return $embargoed;
377
    }
378
379
    /**
380
     * Hides the document, so it does not show up when getByPage($myPage) is
381
     * called. Automatically un-hides the Document at a specific date.
382
     *
383
     * @param string $datetime date time value when this Document should expire.
384
     * @param bool $write
385
     *
386
     * @return DMSDocument
387
     */
388 View Code Duplication
    public function embargoUntilDate($datetime, $write = true)
0 ignored issues
show
Duplication introduced by helpfulrobot
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
389
    {
390
        $this->EmbargoedUntilDate = DBField::create_field('SS_Datetime', $datetime)->Format('Y-m-d H:i:s');
391
392
        if ($write) {
393
            $this->write();
394
        }
395
396
        return $this;
397
    }
398
399
    /**
400
     * Clears any previously set embargos, so the Document always shows up in
401
     * all queries.
402
     *
403
     * @param bool $write
404
     *
405
     * @return DMSDocument
406
     */
407
    public function clearEmbargo($write = true)
408
    {
409
        $this->EmbargoedIndefinitely = false;
410
        $this->EmbargoedUntilPublished = false;
411
        $this->EmbargoedUntilDate = null;
412
413
        if ($write) {
414
            $this->write();
415
        }
416
417
        return $this;
418
    }
419
420
    /**
421
     * Returns if this is Document is expired.
422
     *
423
     * @return bool
424
     */
425
    public function isExpired()
426
    {
427
        if (is_object($this->ExpireAtDate)) {
428
            $this->ExpireAtDate = $this->ExpireAtDate->Value;
0 ignored issues
show
Bug introduced by helpfulrobot
The property Value does not seem to exist in DateTime.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
429
        }
430
431
        $expired = false;
432
433
        if (!empty($this->ExpireAtDate)) {
434
            if (SS_Datetime::now()->Value >= $this->ExpireAtDate) {
435
                $expired = true;
436
            }
437
        }
438
439
        return $expired;
440
    }
441
442
    /**
443
     * Hides the document at a specific date, so it does not show up when
444
     * getByPage($myPage) is called.
445
     *
446
     * @param string $datetime date time value when this Document should expire
447
     * @param bool $write
448
     *
449
     * @return DMSDocument
450
     */
451 View Code Duplication
    public function expireAtDate($datetime, $write = true)
0 ignored issues
show
Duplication introduced by helpfulrobot
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
452
    {
453
        $this->ExpireAtDate = DBField::create_field('SS_Datetime', $datetime)->Format('Y-m-d H:i:s');
454
455
        if ($write) {
456
            $this->write();
457
        }
458
459
        return $this;
460
    }
461
462
    /**
463
     * Clears any previously set expiry.
464
     *
465
     * @param bool $write
466
     *
467
     * @return DMSDocument
468
     */
469
    public function clearExpiry($write = true)
470
    {
471
        $this->ExpireAtDate = null;
472
473
        if ($write) {
474
            $this->write();
475
        }
476
477
        return $this;
478
    }
479
480
    /**
481
     * Returns a DataList of all previous Versions of this document (check the
482
     * LastEdited date of each object to find the correct one).
483
     *
484
     * If {@link DMSDocument_versions::$enable_versions} is disabled then an
485
     * Exception is thrown
486
     *
487
     * @throws Exception
488
     *
489
     * @return DataList List of Document objects
490
     */
491
    public function getVersions()
492
    {
493
        if (!DMSDocument_versions::$enable_versions) {
494
            throw new Exception("DMSDocument versions are disabled");
495
        }
496
497
        return DMSDocument_versions::get_versions($this);
498
    }
499
500
    /**
501
     * Returns the full filename of the document stored in this object.
502
     *
503
     * @return string
0 ignored issues
show
Documentation introduced by helpfulrobot
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
504
     */
505
    public function getFullPath()
506
    {
507
        if ($this->Filename) {
508
            return DMS::inst()->getStoragePath() . DIRECTORY_SEPARATOR
509
                . $this->Folder . DIRECTORY_SEPARATOR . $this->Filename;
510
        }
511
512
        return null;
513
    }
514
515
    /**
516
     * Returns the filename of this asset.
517
     *
518
     * @return string
519
     */
520
    public function getFilename()
521
    {
522
        if ($this->getField('Filename')) {
523
            return $this->getField('Filename');
524
        }
525
        return ASSETS_DIR . '/';
526
    }
527
528
    /**
529
     * @return string
530
     */
531
    public function getName()
532
    {
533
        return $this->getField('Title');
534
    }
535
536
537
    /**
538
     * Returns the filename of a document without the prefix, e.g. 0~filename.jpg -> filename.jpg
539
     *
540
     * @return string
541
     */
542
    public function getFilenameWithoutID()
543
    {
544
        $filenameParts = explode('~', $this->Filename);
545
        $filename = array_pop($filenameParts);
546
547
        return $filename;
548
    }
549
550
    /**
551
     * @return string
552
     */
553
    public function getStorageFolder()
554
    {
555
        return DMS::inst()->getStoragePath() . DIRECTORY_SEPARATOR . DMS::inst()->getStorageFolder($this->ID);
556
    }
557
558
    /**
559
     * Deletes the DMSDocument and its underlying file. Also calls the parent DataObject's delete method in
560
     * order to complete an cascade.
561
     *
562
     * @return void
563
     */
564
    public function delete()
565
    {
566
        // delete the file (and previous versions of files)
567
        $filesToDelete = array();
568
        $storageFolder = $this->getStorageFolder();
569
570
        if (file_exists($storageFolder)) {
571
            if ($handle = opendir($storageFolder)) {
572
                while (false !== ($entry = readdir($handle))) {
573
                    // only delete if filename starts the the relevant ID
574
                    if (strpos($entry, $this->ID.'~') === 0) {
575
                        $filesToDelete[] = $entry;
576
                    }
577
                }
578
579
                closedir($handle);
580
581
                //delete all this files that have the id of this document
582
                foreach ($filesToDelete as $file) {
583
                    $filePath = $storageFolder .DIRECTORY_SEPARATOR . $file;
584
585
                    if (is_file($filePath)) {
586
                        unlink($filePath);
587
                    }
588
                }
589
            }
590
        }
591
592
        // get rid of any versions have saved for this DMSDocument, too
593
        if (DMSDocument_versions::$enable_versions) {
594
            $versions = $this->getVersions();
595
596
            if ($versions->Count() > 0) {
597
                foreach ($versions as $v) {
598
                    $v->delete();
599
                }
600
            }
601
        }
602
603
        return parent::delete();
604
    }
605
606
    /**
607
     * Relate an existing file on the filesystem to the document.
608
     *
609
     * Copies the file to the new destination, as defined in {@link DMS::getStoragePath()}.
610
     *
611
     * @param string $filePath Path to file, relative to webroot.
612
     *
613
     * @return DMSDocument
614
     */
615
    public function storeDocument($filePath)
616
    {
617
        if (empty($this->ID)) {
618
            user_error("Document must be written to database before it can store documents", E_USER_ERROR);
619
        }
620
621
        // calculate all the path to copy the file to
622
        $fromFilename = basename($filePath);
623
        $toFilename = $this->ID. '~' . $fromFilename; //add the docID to the start of the Filename
624
        $toFolder = DMS::inst()->getStorageFolder($this->ID);
625
        $toPath = DMS::inst()->getStoragePath() . DIRECTORY_SEPARATOR . $toFolder . DIRECTORY_SEPARATOR . $toFilename;
626
627
        DMS::inst()->createStorageFolder(DMS::inst()->getStoragePath() . DIRECTORY_SEPARATOR . $toFolder);
628
629
        //copy the file into place
630
        $fromPath = BASE_PATH . DIRECTORY_SEPARATOR . $filePath;
631
632
        //version the existing file (copy it to a new "very specific" filename
633
        if (DMSDocument_versions::$enable_versions) {
634
            DMSDocument_versions::create_version($this);
635
        } else {    //otherwise delete the old document file
636
            $oldPath = $this->getFullPath();
637
            if (file_exists($oldPath)) {
638
                unlink($oldPath);
639
            }
640
        }
641
642
        copy($fromPath, $toPath);   //this will overwrite the existing file (if present)
643
644
        //write the filename of the stored document
645
        $this->Filename = $toFilename;
646
        $this->Folder = strval($toFolder);
647
648
        $extension = pathinfo($this->Filename, PATHINFO_EXTENSION);
649
650
        if (empty($this->Title)) {
651
            // don't overwrite existing document titles
652
            $this->Title = basename($filePath, '.'.$extension);
653
        }
654
655
        $this->write();
656
657
        return $this;
658
    }
659
660
    /**
661
     * Takes a File object or a String (path to a file) and copies it into the
662
     * DMS, replacing the original document file but keeping the rest of the
663
     * document unchanged.
664
     *
665
     * @param File|string $file path to a file to store
666
     *
667
     * @return DMSDocument object that we replaced the file in
668
     */
669
    public function replaceDocument($file)
670
    {
671
        $filePath = DMS::inst()->transformFileToFilePath($file);
672
        $doc = $this->storeDocument($filePath); // replace the document
673
674
        return $doc;
675
    }
676
677
678
    /**
679
     * Return the type of file for the given extension
680
     * on the current file name.
681
     *
682
     * @param string $ext
683
     *
684
     * @return string
685
     */
686
    public static function get_file_type($ext)
687
    {
688
        $types = array(
689
            'gif' => 'GIF image - good for diagrams',
690
            'jpg' => 'JPEG image - good for photos',
691
            'jpeg' => 'JPEG image - good for photos',
692
            'png' => 'PNG image - good general-purpose format',
693
            'ico' => 'Icon image',
694
            'tiff' => 'Tagged image format',
695
            'doc' => 'Word document',
696
            'xls' => 'Excel spreadsheet',
697
            'zip' => 'ZIP compressed file',
698
            'gz' => 'GZIP compressed file',
699
            'dmg' => 'Apple disk image',
700
            'pdf' => 'Adobe Acrobat PDF file',
701
            'mp3' => 'MP3 audio file',
702
            'wav' => 'WAV audo file',
703
            'avi' => 'AVI video file',
704
            'mpg' => 'MPEG video file',
705
            'mpeg' => 'MPEG video file',
706
            'js' => 'Javascript file',
707
            'css' => 'CSS file',
708
            'html' => 'HTML file',
709
            'htm' => 'HTML file'
710
        );
711
712
        return isset($types[$ext]) ? $types[$ext] : $ext;
713
    }
714
715
716
    /**
717
     * Returns the Description field with HTML <br> tags added when there is a
718
     * line break.
719
     *
720
     * @return string
721
     */
722
    public function getDescriptionWithLineBreak()
723
    {
724
        return nl2br($this->getField('Description'));
725
    }
726
727
    /**
728
     * @return FieldList
729
     */
730
    public function getCMSFields()
731
    {
732
        //include JS to handling showing and hiding of bottom "action" tabs
733
        Requirements::javascript(DMS_DIR . '/javascript/DMSDocumentCMSFields.js');
734
        Requirements::css(DMS_DIR . '/dist/css/cmsbundle.css');
735
736
        $fields = new FieldList();  //don't use the automatic scaffolding, it is slow and unnecessary here
737
738
        $extraTasks = '';   //additional text to inject into the list of tasks at the bottom of a DMSDocument CMSfield
0 ignored issues
show
Unused Code introduced by helpfulrobot
$extraTasks is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
739
740
        //get list of shortcode page relations
741
        $relationFinder = new ShortCodeRelationFinder();
742
        $relationList = $relationFinder->getList($this->ID);
743
744
        $fieldsTop = $this->getFieldsForFile($relationList->count());
745
        $fields->add($fieldsTop);
746
747
        $fields->add(TextField::create('Title', _t('DMSDocument.TITLE', 'Title')));
748
        $fields->add(TextareaField::create('Description', _t('DMSDocument.DESCRIPTION', 'Description')));
749
750
        $coverImageField = UploadField::create('CoverImage', _t('DMSDocument.COVERIMAGE', 'Cover Image'));
751
        $coverImageField->getValidator()->setAllowedExtensions(array('jpg', 'jpeg', 'png', 'gif'));
752
        $coverImageField->setConfig('allowedMaxFileNumber', 1);
753
        $fields->add($coverImageField);
754
755
756
        $downloadBehaviorSource = array(
757
            'open' => _t('DMSDocument.OPENINBROWSER', 'Open in browser'),
758
            'download' => _t('DMSDocument.FORCEDOWNLOAD', 'Force download'),
759
        );
760
        $defaultDownloadBehaviour = Config::inst()->get('DMSDocument', 'default_download_behaviour');
761
        if (!isset($downloadBehaviorSource[$defaultDownloadBehaviour])) {
762
            user_error('Default download behaviour "' . $defaultDownloadBehaviour . '" not supported.', E_USER_WARNING);
763
        } else {
764
            $downloadBehaviorSource[$defaultDownloadBehaviour] .= ' (' . _t('DMSDocument.DEFAULT', 'default') . ')';
765
        }
766
767
        $fields->add(
768
            OptionsetField::create(
769
                'DownloadBehavior',
770
                _t('DMSDocument.DOWNLOADBEHAVIOUR', 'Download behavior'),
771
                $downloadBehaviorSource,
772
                $defaultDownloadBehaviour
773
            )
774
            ->setDescription(
775
                'How the visitor will view this file. <strong>Open in browser</strong> '
776
                . 'allows files to be opened in a new tab.'
777
            )
778
        );
779
780
        //create upload field to replace document
781
        $uploadField = new DMSUploadField('ReplaceFile', 'Replace file');
782
        $uploadField->setConfig('allowedMaxFileNumber', 1);
783
        $uploadField->setConfig('downloadTemplateName', 'ss-dmsuploadfield-downloadtemplate');
784
        $uploadField->setRecord($this);
785
786
        $gridFieldConfig = GridFieldConfig::create()->addComponents(
787
            new GridFieldToolbarHeader(),
788
            new GridFieldSortableHeader(),
789
            new GridFieldDataColumns(),
790
            new GridFieldPaginator(30),
791
            //new GridFieldEditButton(),
0 ignored issues
show
Unused Code Comprehensibility introduced by helpfulrobot
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
792
            new GridFieldDetailForm()
793
        );
794
795
        $gridFieldConfig->getComponentByType('GridFieldDataColumns')
0 ignored issues
show
Bug introduced by helpfulrobot
It seems like you code against a concrete implementation and not the interface GridFieldComponent as the method setDisplayFields() does only exist in the following implementations of said interface: GridFieldDataColumns, GridFieldEditableColumns, GridFieldExternalLink.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
796
            ->setDisplayFields(array(
797
                'Title' => 'Title',
798
                'ClassName' => 'Page Type',
799
                'ID' => 'Page ID'
800
            ))
801
            ->setFieldFormatting(array(
802
                'Title' => sprintf(
803
                    '<a class=\"cms-panel-link\" href=\"%s/$ID\">$Title</a>',
804
                    singleton('CMSPageEditController')->Link('show')
805
                )
806
            ));
807
808
        $pagesGrid = GridField::create(
809
            'Pages',
810
            _t('DMSDocument.RelatedPages', 'Related Pages'),
811
            $this->getRelatedPages(),
812
            $gridFieldConfig
813
        );
814
815
        $referencesGrid = GridField::create(
816
            'References',
817
            _t('DMSDocument.RelatedReferences', 'Related References'),
818
            $relationList,
819
            $gridFieldConfig
820
        );
821
822
        if (DMSDocument_versions::$enable_versions) {
823
            $versionsGridFieldConfig = GridFieldConfig::create()->addComponents(
824
                new GridFieldToolbarHeader(),
825
                new GridFieldSortableHeader(),
826
                new GridFieldDataColumns(),
827
                new GridFieldPaginator(30)
828
            );
829
            $versionsGridFieldConfig->getComponentByType('GridFieldDataColumns')
0 ignored issues
show
Bug introduced by Robbie Averill
It seems like you code against a concrete implementation and not the interface GridFieldComponent as the method setDisplayFields() does only exist in the following implementations of said interface: GridFieldDataColumns, GridFieldEditableColumns, GridFieldExternalLink.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
830
                ->setDisplayFields(Config::inst()->get('DMSDocument_versions', 'display_fields'))
831
                ->setFieldFormatting(
832
                    array(
833
                        'FilenameWithoutID' => '<a target="_blank" class="file-url" href="$Link">'
834
                            . '$FilenameWithoutID</a>'
835
                    )
836
                );
837
838
            $versionsGrid =  GridField::create(
839
                'Versions',
840
                _t('DMSDocument.Versions', 'Versions'),
841
                $this->getVersions(),
842
                $versionsGridFieldConfig
843
            );
844