Completed
Pull Request — master (#110)
by Franco
02:04
created

DMSDocument::getPermissionDeniedReason()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 4
nop 0
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 DateTime LastChanged
12
 * @property Boolean EmbargoedIndefinitely
13
 * @property Boolean EmbargoedUntilPublished
14
 * @property DateTime EmbargoedUntilDate
15
 * @property DateTime ExpireAtDate
16
 * @property Enum DownloadBehavior
17
 * @property Enum CanViewType Enum('Anyone, LoggedInUsers, OnlyTheseUsers', 'Anyone')
18
 * @property Enum CanEditType Enum('LoggedInUsers, OnlyTheseUsers', 'LoggedInUsers')
19
 *
20
 * @method ManyManyList RelatedDocuments
21
 * @method ManyManyList Tags
22
 * @method ManyManyList ViewerGroups
23
 * @method ManyManyList EditorGroups
24
 *
25
 */
26
class DMSDocument extends DataObject implements DMSDocumentInterface
0 ignored issues
show
Coding Style Compatibility introduced by
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...
27
{
28
    private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
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...
29
        "Filename" => "Varchar(255)", // eg. 3469~2011-energysaving-report.pdf
30
        "Folder" => "Varchar(255)",    // eg.	0
31
        "Title" => 'Varchar(1024)', // eg. "Energy Saving Report for Year 2011, New Zealand LandCorp"
32
        "Description" => 'Text',
33
        "ViewCount" => 'Int',
34
        // When this document's file was created or last replaced (small changes like updating title don't count)
35
        "LastChanged" => 'SS_DateTime',
36
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 $many_many = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
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...
47
        'Pages' => 'SiteTree',
48
        'RelatedDocuments' => 'DMSDocument',
49
        'Tags' => 'DMSTag',
50
        'ViewerGroups' => 'Group',
51
        'EditorGroups' => 'Group',
52
    );
53
54
    private static $many_many_extraFields = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $many_many_extraFields 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...
55
        'Pages' => array(
56
            'DocumentSort' => 'Int'
57
        )
58
    );
59
60
    private static $display_fields = array(
0 ignored issues
show
Unused Code introduced by
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...
61
        'ID' => 'ID',
62
        'Title' => 'Title',
63
        'FilenameWithoutID' => 'Filename',
64
        'LastChanged' => 'LastChanged'
65
    );
66
67
    private static $singular_name = 'Document';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
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...
68
69
    private static $plural_name = 'Documents';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
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...
70
71
    private static $searchable_fields = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $searchable_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...
72
        'ID' => array(
73
            'filter' => 'ExactMatchFilter',
74
            'field' => 'NumericField'
75
        ),
76
        'Title',
77
        'Filename',
78
        'LastChanged'
79
    );
80
81
    private static $summary_fields = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
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...
82
        'Filename' => 'Filename',
83
        'Title' => 'Title',
84
        'ViewCount' => 'ViewCount',
85
        'getPages.count' => 'Page Use'
86
    );
87
88
    /**
89
     * @var string download|open
90
     * @config
91
     */
92
    private static $default_download_behaviour = 'download';
0 ignored issues
show
Unused Code introduced by
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...
93
94
    public function canView($member = null)
95
    {
96
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
97
            $member = Member::currentUser();
98
        }
99
100
        // extended access checks
101
        $results = $this->extend('canView', $member);
102
103
        if ($results && is_array($results)) {
0 ignored issues
show
Bug Best Practice introduced by
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...
104
            if (!min($results)) {
105
                return false;
106
            }
107
        }
108
109
        if (!$this->CanViewType || $this->CanViewType == 'Anyone') {
110
            return true;
111
        }
112
113 View Code Duplication
        if ($member && Permission::checkMember($member,
0 ignored issues
show
Duplication introduced by
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...
114
                array(
115
                    'ADMIN',
116
                    'SITETREE_EDIT_ALL',
117
                    'SITETREE_VIEW_ALL',
118
                )
119
            )
120
        ) {
121
            return true;
122
        }
123
124
        if ($this->isHidden()) {
125
            return false;
126
        }
127
128
        if ($this->CanViewType == 'LoggedInUsers') {
129
            return $member && $member->exists();
130
        }
131
132
        if ($this->CanViewType == 'OnlyTheseUsers' && $this->ViewerGroups()->count()) {
0 ignored issues
show
Documentation Bug introduced by
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...
133
            return ($member && $member->inGroups($this->ViewerGroups()));
0 ignored issues
show
Documentation Bug introduced by
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...
134
        }
135
136
        return $this->canEdit($member);
137
    }
138
139
    public function canEdit($member = null)
140
    {
141
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
142
            $member = Member::currentUser();
143
        }
144
145
        $results = $this->extend('canEdit', $member);
146
147
        if ($results && is_array($results)) {
0 ignored issues
show
Bug Best Practice introduced by
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...
148
            if (!min($results)) {
149
                return false;
150
            }
151
        }
152
153
        // Do early admin check
154 View Code Duplication
        if ($member && Permission::checkMember($member,
0 ignored issues
show
Duplication introduced by
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...
155
                array(
156
                    'ADMIN',
157
                    'SITETREE_EDIT_ALL',
158
                    'SITETREE_VIEW_ALL',
159
                )
160
            )
161
        ) {
162
            return true;
163
        }
164
165
        if ($this->CanEditType === 'LoggedInUsers') {
166
            return $member && $member->exists();
167
        }
168
169
        if ($this->CanEditType === 'OnlyTheseUsers' && $this->EditorGroups()->count()) {
0 ignored issues
show
Documentation Bug introduced by
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...
170
            return $member && $member->inGroups($this->EditorGroups());
0 ignored issues
show
Documentation Bug introduced by
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...
171
        }
172
173
        return ($member && Permission::checkMember($member, array('ADMIN', 'SITETREE_EDIT_ALL')));
174
    }
175
176
    /**
177
     * @param Member $member
0 ignored issues
show
Documentation introduced by
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...
178
     *
179
     * @return boolean
180
     */
181
    public function canCreate($member = null)
182
    {
183
        $results = $this->extend('canCreate', $member);
184
185
        if ($results && is_array($results)) {
0 ignored issues
show
Bug Best Practice introduced by
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...
186
            if (!min($results)) {
187
                return false;
188
            }
189
        }
190
191
        return $this->canEdit($member);
192
    }
193
194
    /**
195
     * @param Member $member
0 ignored issues
show
Documentation introduced by
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...
196
     *
197
     * @return boolean
198
     */
199
    public function canDelete($member = null)
200
    {
201
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
202
            $member = Member::currentUser();
203
        }
204
205
        $results = $this->extend('canDelete', $member);
206
207
        if ($results && is_array($results)) {
0 ignored issues
show
Bug Best Practice introduced by
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...
208
            if (!min($results)) {
209
                return false;
210
            }
211
        }
212
213
        return $this->canView();
214
    }
215
216
217
218
    /**
219
     * Associates this document with a Page. This method does nothing if the
220
     * association already exists.
221
     *
222
     * This could be a simple wrapper around $myDoc->Pages()->add($myPage) to
223
     * add a many_many relation.
224
     *
225
     * @param SiteTree $pageObject Page object to associate this Document with
226
     *
227
     * @return DMSDocument
228
     */
229
    public function addPage($pageObject)
230
    {
231
        $this->Pages()->add($pageObject);
232
233
        DB::query(
234
            "UPDATE \"DMSDocument_Pages\" SET \"DocumentSort\"=\"DocumentSort\"+1"
235
            . " WHERE \"SiteTreeID\" = $pageObject->ID"
236
        );
237
238
        return $this;
239
    }
240
241
    /**
242
     * Associates this DMSDocument with a set of Pages. This method loops
243
     * through a set of page ids, and then associates this DMSDocument with the
244
     * individual Page with the each page id in the set.
245
     *
246
     * @param array $pageIDs
247
     *
248
     * @return DMSDocument
249
     */
250
    public function addPages($pageIDs)
251
    {
252
        foreach ($pageIDs as $id) {
253
            $pageObject = DataObject::get_by_id("SiteTree", $id);
254
255
            if ($pageObject && $pageObject->exists()) {
256
                $this->addPage($pageObject);
0 ignored issues
show
Compatibility introduced by
$pageObject of type object<DataObject> is not a sub-type of object<SiteTree>. It seems like you assume a child class of the class DataObject to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
257
            }
258
        }
259
260
        return $this;
261
    }
262
263
    /**
264
     * Removes the association between this Document and a Page. This method
265
     * does nothing if the association does not exist.
266
     *
267
     * @param SiteTree $pageObject Page object to remove the association to
268
     *
269
     * @return DMSDocument
270
     */
271
    public function removePage($pageObject)
272
    {
273
        $this->Pages()->remove($pageObject);
0 ignored issues
show
Documentation introduced by
$pageObject is of type object<SiteTree>, but the function expects a object<DataClass>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
274
275
        return $this;
276
    }
277
278
    /**
279
     * @see getPages()
280
     *
281
     * @return DataList
282
     */
283
    public function Pages()
284
    {
285
        $pages = $this->getManyManyComponents('Pages');
286
        $this->extend('updatePages', $pages);
287
288
        return $pages;
289
    }
290
291
    /**
292
     * Returns a list of the Page objects associated with this Document.
293
     *
294
     * @return DataList
295
     */
296
    public function getPages()
297
    {
298
        return $this->Pages();
299
    }
300
301
    /**
302
     * Removes all associated Pages from the DMSDocument
303
     *
304
     * @return DMSDocument
305
     */
306
    public function removeAllPages()
307
    {
308
        $this->Pages()->removeAll();
309
310
        return $this;
311
    }
312
313
    /**
314
     * Increase ViewCount by 1, without update any other record fields such as
315
     * LastEdited.
316
     *
317
     * @return DMSDocument
318
     */
319
    public function trackView()
320
    {
321
        if ($this->ID > 0) {
322
            $count = $this->ViewCount + 1;
323
324
            $this->ViewCount = $count;
325
326
            DB::query("UPDATE \"DMSDocument\" SET \"ViewCount\"='$count' WHERE \"ID\"={$this->ID}");
327
        }
328
329
        return $this;
330
    }
331
332
333
    /**
334
     * Adds a metadata tag to the Document. The tag has a category and a value.
335
     *
336
     * Each category can have multiple values by default. So:
337
     * addTag("fruit","banana") addTag("fruit", "apple") will add two items.
338
     *
339
     * However, if the third parameter $multiValue is set to 'false', then all
340
     * updates to a category only ever update a single value. So:
341
     * addTag("fruit","banana") addTag("fruit", "apple") would result in a
342
     * single metadata tag: fruit->apple.
343
     *
344
     * Can could be implemented as a key/value store table (although it is more
345
     * like category/value, because the same category can occur multiple times)
346
     *
347
     * @param string $category of a metadata category to add (required)
348
     * @param string $value of a metadata value to add (required)
349
     * @param bool $multiValue Boolean that determines if the category is
350
     *                  multi-value or single-value (optional)
351
     *
352
     * @return DMSDocument
353
     */
354
    public function addTag($category, $value, $multiValue = true)
355
    {
356
        if ($multiValue) {
357
            //check for a duplicate tag, don't add the duplicate
358
            $currentTag = $this->Tags()->filter(array('Category' => $category, 'Value' => $value));
0 ignored issues
show
Documentation Bug introduced by
The method Tags 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...
359
            if ($currentTag->Count() == 0) {
360
                //multi value tag
361
                $tag = new DMSTag();
362
                $tag->Category = $category;
0 ignored issues
show
Documentation introduced by
The property Category does not exist on object<DMSTag>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
363
                $tag->Value = $value;
0 ignored issues
show
Documentation introduced by
The property Value does not exist on object<DMSTag>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
364
                $tag->MultiValue = true;
0 ignored issues
show
Documentation introduced by
The property MultiValue does not exist on object<DMSTag>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
365
                $tag->write();
366
                $tag->Documents()->add($this);
0 ignored issues
show
Documentation Bug introduced by
The method Documents does not exist on object<DMSTag>? 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...
367
            } else {
368
                //add the relation between the tag and document
369
                foreach ($currentTag as $tagObj) {
370
                    $tagObj->Documents()->add($this);
371
                }
372
            }
373
        } else {
374
            //single value tag
375
            $currentTag = $this->Tags()->filter(array('Category' => $category));
0 ignored issues
show
Documentation Bug introduced by
The method Tags 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...
376
            $tag = null;
0 ignored issues
show
Unused Code introduced by
$tag 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...
377
            if ($currentTag->Count() == 0) {
378
                //create the single-value tag
379
                $tag = new DMSTag();
380
                $tag->Category = $category;
0 ignored issues
show
Documentation introduced by
The property Category does not exist on object<DMSTag>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
381
                $tag->Value = $value;
0 ignored issues
show
Documentation introduced by
The property Value does not exist on object<DMSTag>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
382
                $tag->MultiValue = false;
0 ignored issues
show
Documentation introduced by
The property MultiValue does not exist on object<DMSTag>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
383
                $tag->write();
384
            } else {
385
                //update the single value tag
386
                $tag = $currentTag->first();
387
                $tag->Value = $value;
388
                $tag->MultiValue = false;
389
                $tag->write();
390
            }
391
392
            // regardless of whether we created a new tag or are just updating an
393
            // existing one, add the relation
394
            $tag->Documents()->add($this);
395
        }
396
397
        return $this;
398
    }
399
400
    /**
401
     * @param string $category
402
     * @param string $value
0 ignored issues
show
Documentation introduced by
Should the type for parameter $value not be string|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...
403
     *
404
     * @return DataList
405
     */
406
    protected function getTagsObjects($category, $value = null)
407
    {
408
        $valueFilter = array("Category" => $category);
409
        if (!empty($value)) {
410
            $valueFilter['Value'] = $value;
411
        }
412
413
        $tags = $this->Tags()->filter($valueFilter);
0 ignored issues
show
Documentation Bug introduced by
The method Tags 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...
414
        return $tags;
415
    }
416
417
    /**
418
     * Fetches all tags associated with this DMSDocument within a given
419
     * category. If a value is specified this method tries to fetch that
420
     * specific tag.
421
     *
422
     * @param string $category metadata category to get
423
     * @param string $value value of the tag to get
0 ignored issues
show
Documentation introduced by
Should the type for parameter $value not be string|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...
424
     *
425
     * @return array Strings of all the tags or null if there is no match found
0 ignored issues
show
Documentation introduced by
Should the return type not be array|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...
426
     */
427
    public function getTagsList($category, $value = null)
428
    {
429
        $tags = $this->getTagsObjects($category, $value);
430
431
        $returnArray = null;
432
433
        if ($tags->Count() > 0) {
434
            $returnArray = array();
435
436
            foreach ($tags as $t) {
437
                $returnArray[] = $t->Value;
438
            }
439
        }
440
441
        return $returnArray;
442
    }
443
444
    /**
445
     * Removes a tag from the Document. If you only set a category, then all
446
     * values in that category are deleted.
447
     *
448
     * If you specify both a category and a value, then only that single
449
     * category/value pair is deleted.
450
     *
451
     * Nothing happens if the category or the value do not exist.
452
     *
453
     * @param string $category Category to remove
454
     * @param string $value Value to remove
0 ignored issues
show
Documentation introduced by
Should the type for parameter $value not be string|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...
455
     *
456
     * @return DMSDocument
457
     */
458
    public function removeTag($category, $value = null)
459
    {
460
        $tags = $this->getTagsObjects($category, $value);
461
462
        if ($tags->Count() > 0) {
463
            foreach ($tags as $t) {
464
                $documentList = $t->Documents();
465
466
                //remove the relation between the tag and the document
467
                $documentList->remove($this);
468
469
                //delete the entire tag if it has no relations left
470
                if ($documentList->Count() == 0) {
471
                    $t->delete();
472
                }
473
            }
474
        }
475
476
        return $this;
477
    }
478
479
    /**
480
     * Deletes all tags associated with this Document.
481
     *
482
     * @return DMSDocument
483
     */
484
    public function removeAllTags()
485
    {
486
        $allTags = $this->Tags();
0 ignored issues
show
Documentation Bug introduced by
The method Tags 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...
487
488
        foreach ($allTags as $tag) {
489
            $documentlist = $tag->Documents();
490
            $documentlist->remove($this);
491
            if ($tag->Documents()->Count() == 0) {
492
                $tag->delete();
493
            }
494
        }
495
496
        return $this;
497
    }
498
499
    /**
500
     * Returns a link to download this document from the DMS store.
501
     * Alternatively a basic javascript alert will be shown should the user not have view permissions. An extension
502
     * point for this was also added.
503
     *
504
     * To extend use the following from within an Extension subclass:
505
     *
506
     * <code>
507
     * public function updateGetLink($result)
508
     * {
509
     *     // Do something here
510
     * }
511
     * </code>
512
     *
513
     * @return string
514
     */
515
    public function getLink()
516
    {
517
        $result = Controller::join_links(Director::baseURL(), 'dmsdocument/' . $this->ID);
518
        if (!$this->canView()) {
519
            $result = sprintf("javascript:alert('%s')", $this->getPermissionDeniedReason());
520
        }
521
522
        $this->extend('updateGetLink', $result);
523
524
        return $result;
525
    }
526
527
    /**
528
     * @return string
529
     */
530
    public function Link()
531
    {
532
        return $this->getLink();
533
    }
534
535
    /**
536
     * Hides the document, so it does not show up when getByPage($myPage) is
537
     * called (without specifying the $showEmbargoed = true parameter).
538
     *
539
     * This is similar to expire, except that this method should be used to hide
540
     * documents that have not yet gone live.
541
     *
542
     * @param bool $write Save change to the database
543
     *
544
     * @return DMSDocument
545
     */
546
    public function embargoIndefinitely($write = true)
547
    {
548
        $this->EmbargoedIndefinitely = true;
549
550
        if ($write) {
551
            $this->write();
552
        }
553
554
        return $this;
555
    }
556
557
    /**
558
     * Hides the document until any page it is linked to is published
559
     *
560
     * @param bool $write Save change to database
561
     *
562
     * @return DMSDocument
563
     */
564
    public function embargoUntilPublished($write = true)
565
    {
566
        $this->EmbargoedUntilPublished = true;
567
568
        if ($write) {
569
            $this->write();
570
        }
571
572
        return $this;
573
    }
574
575
    /**
576
     * Returns if this is Document is embargoed or expired.
577
     *
578
     * Also, returns if the document should be displayed on the front-end,
579
     * respecting the current reading mode of the site and the embargo status.
580
     *
581
     * I.e. if a document is embargoed until published, then it should still
582
     * show up in draft mode.
583
     *
584
     * @return bool
585
     */
586
    public function isHidden()
587
    {
588
        $hidden = $this->isEmbargoed() || $this->isExpired();
589
        $readingMode = Versioned::get_reading_mode();
590
591
        if ($readingMode == "Stage.Stage") {
592
            if ($this->EmbargoedUntilPublished == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
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...
593
                $hidden = false;
594
            }
595
        }
596
597
        return $hidden;
598
    }
599
600
    /**
601
     * Returns if this is Document is embargoed.
602
     *
603
     * @return bool
604
     */
605
    public function isEmbargoed()
606
    {
607
        if (is_object($this->EmbargoedUntilDate)) {
608
            $this->EmbargoedUntilDate = $this->EmbargoedUntilDate->Value;
0 ignored issues
show
Bug introduced by
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...
609
        }
610
611
        $embargoed = false;
612
613
        if ($this->EmbargoedIndefinitely) {
614
            $embargoed = true;
615
        } elseif ($this->EmbargoedUntilPublished) {
616
            $embargoed = true;
617
        } elseif (!empty($this->EmbargoedUntilDate)) {
618
            if (SS_Datetime::now()->Value < $this->EmbargoedUntilDate) {
619
                $embargoed = true;
620
            }
621
        }
622
623
        return $embargoed;
624
    }
625
626
    /**
627
     * Hides the document, so it does not show up when getByPage($myPage) is
628
     * called. Automatically un-hides the Document at a specific date.
629
     *
630
     * @param string $datetime date time value when this Document should expire.
631
     * @param bool $write
632
     *
633
     * @return DMSDocument
634
     */
635 View Code Duplication
    public function embargoUntilDate($datetime, $write = true)
0 ignored issues
show
Duplication introduced by
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...
636
    {
637
        $this->EmbargoedUntilDate = DBField::create_field('SS_Datetime', $datetime)->Format('Y-m-d H:i:s');
638
639
        if ($write) {
640
            $this->write();
641
        }
642
643
        return $this;
644
    }
645
646
    /**
647
     * Clears any previously set embargos, so the Document always shows up in
648
     * all queries.
649
     *
650
     * @param bool $write
651
     *
652
     * @return DMSDocument
653
     */
654
    public function clearEmbargo($write = true)
655
    {
656
        $this->EmbargoedIndefinitely = false;
657
        $this->EmbargoedUntilPublished = false;
658
        $this->EmbargoedUntilDate = null;
659
660
        if ($write) {
661
            $this->write();
662
        }
663
664
        return $this;
665
    }
666
667
    /**
668
     * Returns if this is Document is expired.
669
     *
670
     * @return bool
671
     */
672
    public function isExpired()
673
    {
674
        if (is_object($this->ExpireAtDate)) {
675
            $this->ExpireAtDate = $this->ExpireAtDate->Value;
0 ignored issues
show
Bug introduced by
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...
676
        }
677
678
        $expired = false;
679
680
        if (!empty($this->ExpireAtDate)) {
681
            if (SS_Datetime::now()->Value >= $this->ExpireAtDate) {
682
                $expired = true;
683
            }
684
        }
685
686
        return $expired;
687
    }
688
689
    /**
690
     * Hides the document at a specific date, so it does not show up when
691
     * getByPage($myPage) is called.
692
     *
693
     * @param string $datetime date time value when this Document should expire
694
     * @param bool $write
695
     *
696
     * @return DMSDocument
697
     */
698 View Code Duplication
    public function expireAtDate($datetime, $write = true)
0 ignored issues
show
Duplication introduced by
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...
699
    {
700
        $this->ExpireAtDate = DBField::create_field('SS_Datetime', $datetime)->Format('Y-m-d H:i:s');
701
702
        if ($write) {
703
            $this->write();
704
        }
705
706
        return $this;
707
    }
708
709
    /**
710
     * Clears any previously set expiry.
711
     *
712
     * @param bool $write
713
     *
714
     * @return DMSDocument
715
     */
716
    public function clearExpiry($write = true)
717
    {
718
        $this->ExpireAtDate = null;
719
720
        if ($write) {
721
            $this->write();
722
        }
723
724
        return $this;
725
    }
726
727
    /**
728
     * Returns a DataList of all previous Versions of this document (check the
729
     * LastEdited date of each object to find the correct one).
730
     *
731
     * If {@link DMSDocument_versions::$enable_versions} is disabled then an
732
     * Exception is thrown
733
     *
734
     * @throws Exception
735
     *
736
     * @return DataList List of Document objects
737
     */
738
    public function getVersions()
739
    {
740
        if (!DMSDocument_versions::$enable_versions) {
741
            throw new Exception("DMSDocument versions are disabled");
742
        }
743
744
        return DMSDocument_versions::get_versions($this);
745
    }
746
747
    /**
748
     * Returns the full filename of the document stored in this object.
749
     *
750
     * @return string
0 ignored issues
show
Documentation introduced by
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...
751
     */
752
    public function getFullPath()
753
    {
754
        if ($this->Filename) {
755
            return DMS::get_dms_path() . DIRECTORY_SEPARATOR . $this->Folder . DIRECTORY_SEPARATOR . $this->Filename;
756
        }
757
758
        return null;
759
    }
760
761
    /**
762
     * Returns the filename of this asset.
763
     *
764
     * @return string
765
     */
766
    public function getFileName()
767
    {
768
        if ($this->getField('Filename')) {
769
            return $this->getField('Filename');
770
        } else {
771
            return ASSETS_DIR . '/';
772
        }
773
    }
774
775
    /**
776
     * @return string
777
     */
778
    public function getName()
779
    {
780
        return $this->getField('Title');
781
    }
782
783
784
    /**
785
     * @return string
786
     */
787
    public function getFilenameWithoutID()
788
    {
789
        $filenameParts = explode('~', $this->Filename);
790
        $filename = array_pop($filenameParts);
791
792
        return $filename;
793
    }
794
795
    /**
796
     * @return string
797
     */
798
    public function getStorageFolder()
799
    {
800
        return DMS::get_dms_path() . DIRECTORY_SEPARATOR . DMS::get_storage_folder($this->ID);
801
    }
802
803
    /**
804
     * Deletes the DMSDocument, its underlying file, as well as any tags related
805
     * to this DMSDocument. Also calls the parent DataObject's delete method in
806
     * order to complete an cascade.
807
     *
808
     * @return void
809
     */
810
    public function delete()
811
    {
812
        // remove tags
813
        $this->removeAllTags();
814
815
        // delete the file (and previous versions of files)
816
        $filesToDelete = array();
817
        $storageFolder = $this->getStorageFolder();
818
819
        if (file_exists($storageFolder)) {
820
            if ($handle = opendir($storageFolder)) {
821
                while (false !== ($entry = readdir($handle))) {
822
                    // only delete if filename starts the the relevant ID
823
                    if (strpos($entry, $this->ID.'~') === 0) {
824
                        $filesToDelete[] = $entry;
825
                    }
826
                }
827
828
                closedir($handle);
829
830
                //delete all this files that have the id of this document
831
                foreach ($filesToDelete as $file) {
832
                    $filePath = $storageFolder .DIRECTORY_SEPARATOR . $file;
833
834
                    if (is_file($filePath)) {
835
                        unlink($filePath);
836
                    }
837
                }
838
            }
839
        }
840
841
        $this->removeAllPages();
842
843
        // get rid of any versions have saved for this DMSDocument, too
844
        if (DMSDocument_versions::$enable_versions) {
845
            $versions = $this->getVersions();
846
847
            if ($versions->Count() > 0) {
848
                foreach ($versions as $v) {
849
                    $v->delete();
850
                }
851
            }
852
        }
853
854
        parent::delete();
855
    }
856
857
858
859
    /**
860
     * Relate an existing file on the filesystem to the document.
861
     *
862
     * Copies the file to the new destination, as defined in {@link get_DMS_path()}.
863
     *
864
     * @param string $filePath Path to file, relative to webroot.
865
     *
866
     * @return DMSDocument
867
     */
868
    public function storeDocument($filePath)
869
    {
870
        if (empty($this->ID)) {
871
            user_error("Document must be written to database before it can store documents", E_USER_ERROR);
872
        }
873
874
        // calculate all the path to copy the file to
875
        $fromFilename = basename($filePath);
876
        $toFilename = $this->ID. '~' . $fromFilename; //add the docID to the start of the Filename
877
        $toFolder = DMS::get_storage_folder($this->ID);
878
        $toPath = DMS::get_dms_path() . DIRECTORY_SEPARATOR . $toFolder . DIRECTORY_SEPARATOR . $toFilename;
879
880
        DMS::create_storage_folder(DMS::get_dms_path() . DIRECTORY_SEPARATOR . $toFolder);
881
882
        //copy the file into place
883
        $fromPath = BASE_PATH . DIRECTORY_SEPARATOR . $filePath;
884
885
        //version the existing file (copy it to a new "very specific" filename
886
        if (DMSDocument_versions::$enable_versions) {
887
            DMSDocument_versions::create_version($this);
888
        } else {    //otherwise delete the old document file
889
            $oldPath = $this->getFullPath();
890
            if (file_exists($oldPath)) {
891
                unlink($oldPath);
892
            }
893
        }
894
895
        copy($fromPath, $toPath);   //this will overwrite the existing file (if present)
896
897
        //write the filename of the stored document
898
        $this->Filename = $toFilename;
899
        $this->Folder = strval($toFolder);
900
901
        $extension = pathinfo($this->Filename, PATHINFO_EXTENSION);
902
903
        if (empty($this->Title)) {
904
            // don't overwrite existing document titles
905
            $this->Title = basename($filePath, '.'.$extension);
906
        }
907
908
        $this->LastChanged = SS_Datetime::now()->Rfc2822();
909
        $this->write();
910
911
        return $this;
912
    }
913
914
    /**
915
     * Takes a File object or a String (path to a file) and copies it into the
916
     * DMS, replacing the original document file but keeping the rest of the
917
     * document unchanged.
918
     *
919
     * @param File|string $file path to a file to store
920
     *
921
     * @return DMSDocument object that we replaced the file in
922
     */
923
    public function replaceDocument($file)
924
    {
925
        $filePath = DMS::transform_file_to_file_path($file);
926
        $doc = $this->storeDocument($filePath); // replace the document
927
928
        return $doc;
929
    }
930
931
932
    /**
933
     * Return the type of file for the given extension
934
     * on the current file name.
935
     *
936
     * @param string $ext
937
     *
938
     * @return string
939
     */
940
    public static function get_file_type($ext)
941
    {
942
        $types = array(
943
            'gif' => 'GIF image - good for diagrams',
944
            'jpg' => 'JPEG image - good for photos',
945
            'jpeg' => 'JPEG image - good for photos',
946
            'png' => 'PNG image - good general-purpose format',
947
            'ico' => 'Icon image',
948
            'tiff' => 'Tagged image format',
949
            'doc' => 'Word document',
950
            'xls' => 'Excel spreadsheet',
951
            'zip' => 'ZIP compressed file',
952
            'gz' => 'GZIP compressed file',
953
            'dmg' => 'Apple disk image',
954
            'pdf' => 'Adobe Acrobat PDF file',
955
            'mp3' => 'MP3 audio file',
956
            'wav' => 'WAV audo file',
957
            'avi' => 'AVI video file',
958
            'mpg' => 'MPEG video file',
959
            'mpeg' => 'MPEG video file',
960
            'js' => 'Javascript file',
961
            'css' => 'CSS file',
962
            'html' => 'HTML file',
963
            'htm' => 'HTML file'
964
        );
965
966
        return isset($types[$ext]) ? $types[$ext] : $ext;
967
    }
968
969
970
    /**
971
     * Returns the Description field with HTML <br> tags added when there is a
972
     * line break.
973
     *
974
     * @return string
975
     */
976
    public function getDescriptionWithLineBreak()
977
    {
978
        return nl2br($this->getField('Description'));
979
    }
980
981
    /**
982
     * @return FieldList
983
     */
984
    public function getCMSFields()
985
    {
986
        //include JS to handling showing and hiding of bottom "action" tabs
987
        Requirements::javascript(DMS_DIR.'/javascript/DMSDocumentCMSFields.js');
988
        Requirements::css(DMS_DIR.'/css/DMSDocumentCMSFields.css');
989
990
        $fields = new FieldList();  //don't use the automatic scaffolding, it is slow and unnecessary here
991
992
        $extraTasks = '';   //additional text to inject into the list of tasks at the bottom of a DMSDocument CMSfield
993
994
        //get list of shortcode page relations
995
        $relationFinder = new ShortCodeRelationFinder();
996
        $relationList = $relationFinder->getList($this->ID);
997
998
        $fieldsTop = $this->getFieldsForFile($relationList->count());
999
        $fields->add($fieldsTop);
1000
1001
        $fields->add(new TextField('Title', 'Title'));
1002
        $fields->add(new TextareaField('Description', 'Description'));
1003
1004
        $downloadBehaviorSource = array(
1005
            'open' => _t('DMSDocument.OPENINBROWSER', 'Open in browser'),
1006
            'download' => _t('DMSDocument.FORCEDOWNLOAD', 'Force download'),
1007
        );
1008
        $defaultDownloadBehaviour = Config::inst()->get('DMSDocument', 'default_download_behaviour');
1009
        if (!isset($downloadBehaviorSource[$defaultDownloadBehaviour])) {
1010
            user_error('Default download behaviour "' . $defaultDownloadBehaviour . '" not supported.', E_USER_WARNING);
1011
        } else {
1012
            $downloadBehaviorSource[$defaultDownloadBehaviour] .= ' (' . _t('DMSDocument.DEFAULT', 'default') . ')';
1013
        }
1014
1015
        $fields->add(
1016
            OptionsetField::create(
1017
                'DownloadBehavior',
1018
                _t('DMSDocument.DOWNLOADBEHAVIOUR', 'Download behavior'),
1019
                $downloadBehaviorSource,
1020
                $defaultDownloadBehaviour
1021
            )
1022
            ->setDescription(
1023
                'How the visitor will view this file. <strong>Open in browser</strong> '
1024
                . 'allows files to be opened in a new tab.'
1025
            )
1026
        );
1027
1028
        //create upload field to replace document
1029
        $uploadField = new DMSUploadField('ReplaceFile', 'Replace file');
1030
        $uploadField->setConfig('allowedMaxFileNumber', 1);
1031
        $uploadField->setConfig('downloadTemplateName', 'ss-dmsuploadfield-downloadtemplate');
1032
        $uploadField->setRecord($this);
1033
1034
        $gridFieldConfig = GridFieldConfig::create()->addComponents(
1035
            new GridFieldToolbarHeader(),
1036
            new GridFieldSortableHeader(),
1037
            new GridFieldDataColumns(),
1038
            new GridFieldPaginator(30),
1039
            //new GridFieldEditButton(),
0 ignored issues
show
Unused Code Comprehensibility introduced by
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...
1040
            new GridFieldDetailForm()
1041
        );
1042
1043
        $gridFieldConfig->getComponentByType('GridFieldDataColumns')
0 ignored issues
show
Bug introduced by
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...
1044
            ->setDisplayFields(array(
1045
                'Title'=>'Title',
1046
                'ClassName'=>'Page Type',
1047
                'ID'=>'Page ID'
1048
            ))
1049
            ->setFieldFormatting(array(
1050
                'Title'=>sprintf(
1051
                    '<a class=\"cms-panel-link\" href=\"%s/$ID\">$Title</a>',
1052
                    singleton('CMSPageEditController')->Link('show')
1053
                )
1054
            ));
1055
1056
        $pagesGrid = GridField::create(
1057
            'Pages',
1058
            _t('DMSDocument.RelatedPages', 'Related Pages'),
1059
            $this->Pages(),
1060
            $gridFieldConfig
1061
        );
1062
1063
        $referencesGrid = GridField::create(
1064
            'References',
1065
            _t('DMSDocument.RelatedReferences', 'Related References'),
1066
            $relationList,
1067
            $gridFieldConfig
1068
        );
1069
1070
        if (DMSDocument_versions::$enable_versions) {
1071
            $versionsGridFieldConfig = GridFieldConfig::create()->addComponents(
1072
                new GridFieldToolbarHeader(),
1073
                new GridFieldSortableHeader(),
1074
                new GridFieldDataColumns(),
1075
                new GridFieldPaginator(30)
1076
            );
1077
            $versionsGridFieldConfig->getComponentByType('GridFieldDataColumns')
0 ignored issues
show
Bug introduced by
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...
1078
                ->setDisplayFields(Config::inst()->get('DMSDocument_versions', 'display_fields'))
1079
                ->setFieldCasting(array('LastChanged'=>"Datetime->Ago"))
1080
                ->setFieldFormatting(
1081
                    array(
1082
                        'FilenameWithoutID' => '<a target=\'_blank\' class=\'file-url\' href=\'$Link\'>'
1083
                            . '$FilenameWithoutID</a>'
1084
                    )
1085
                );
1086
1087
            $versionsGrid =  GridField::create(
1088
                'Versions',
1089
                _t('DMSDocument.Versions', 'Versions'),
1090
                $this->getVersions(),
1091
                $versionsGridFieldConfig
1092
            );
1093
            $extraTasks .= '<li class="ss-ui-button" data-panel="find-versions">Versions</li>';
1094
        }
1095
1096
        $fields->add(new LiteralField(
1097
            'BottomTaskSelection',
1098
            '<div id="Actions" class="field actions"><label class="left">Actions</label><ul>'
1099
            . '<li class="ss-ui-button" data-panel="embargo">Embargo</li>'
1100
            . '<li class="ss-ui-button" data-panel="expiry">Expiry</li>'
1101
            . '<li class="ss-ui-button" data-panel="replace">Replace</li>'
1102
            . '<li class="ss-ui-button" data-panel="find-usage">Usage</li>'
1103
            . '<li class="ss-ui-button" data-panel="find-references">References</li>'
1104
            . '<li class="ss-ui-button" data-panel="find-relateddocuments">Related Documents</li>'
1105
            . $extraTasks
1106
            . '</ul></div>'
1107
        ));
1108
1109
        $embargoValue = 'None';
1110
        if ($this->EmbargoedIndefinitely) {
1111
            $embargoValue = 'Indefinitely';
1112
        } elseif ($this->EmbargoedUntilPublished) {
1113
            $embargoValue = 'Published';
1114
        } elseif (!empty($this->EmbargoedUntilDate)) {
1115
            $embargoValue = 'Date';
1116
        }
1117
        $embargo = new OptionsetField(
1118
            'Embargo',
1119
            'Embargo',
1120
            array(
1121
                'None' => 'None',
1122
                'Published' => 'Hide document until page is published',
1123
                'Indefinitely' => 'Hide document indefinitely',
1124
                'Date' => 'Hide until set date'
1125
            ),
1126
            $embargoValue
1127
        );
1128
        $embargoDatetime = DatetimeField::create('EmbargoedUntilDate', '');
1129
        $embargoDatetime->getDateField()
1130
            ->setConfig('showcalendar', true)
1131
            ->setConfig('dateformat', 'dd-MM-yyyy')
1132
            ->setConfig('datavalueformat', 'dd-MM-yyyy');
1133
1134
        $expiryValue = 'None';
1135
        if (!empty($this->ExpireAtDate)) {
1136
            $expiryValue = 'Date';
1137
        }
1138
        $expiry = new OptionsetField(
1139
            'Expiry',
1140
            'Expiry',
1141
            array(
1142
                'None' => 'None',
1143
                'Date' => 'Set document to expire on'
1144
            ),
1145
            $expiryValue
1146
        );
1147
        $expiryDatetime = DatetimeField::create('ExpireAtDate', '');
1148
        $expiryDatetime->getDateField()
1149
            ->setConfig('showcalendar', true)
1150
            ->setConfig('dateformat', 'dd-MM-yyyy')
1151
            ->setConfig('datavalueformat', 'dd-MM-yyyy');
1152
1153
        // This adds all the actions details into a group.
1154
        // Embargo, History, etc to go in here
1155
        // These are toggled on and off via the Actions Buttons above
1156
        // exit('hit');
0 ignored issues
show
Unused Code Comprehensibility introduced by
84% 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...
1157
        $actionsPanel = FieldGroup::create(
1158
            FieldGroup::create($embargo, $embargoDatetime)->addExtraClass('embargo'),
1159
            FieldGroup::create($expiry, $expiryDatetime)->addExtraClass('expiry'),
1160
            FieldGroup::create($uploadField)->addExtraClass('replace'),
1161
            FieldGroup::create($pagesGrid)->addExtraClass('find-usage'),
1162
            FieldGroup::create($referencesGrid)->addExtraClass('find-references'),
1163
            FieldGroup::create($versionsGrid)->addExtraClass('find-versions'),
0 ignored issues
show
Bug introduced by
The variable $versionsGrid does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1164
            FieldGroup::create($this->getRelatedDocumentsGridField())->addExtraClass('find-relateddocuments')
1165
        );
1166
1167
        $actionsPanel->setName("ActionsPanel");
1168
        $actionsPanel->addExtraClass("DMSDocumentActionsPanel");
1169
        $fields->push($actionsPanel);
1170
1171
        $this->addPermissionsFields($fields);
1172
        $this->extend('updateCMSFields', $fields);
1173
1174
        return $fields;
1175
    }
1176
1177
    /**
1178
     * Adds permissions selection fields to the FieldList.
1179
     *
1180
     * @param FieldList $fields
1181
     */
1182
    public function addPermissionsFields($fields)
1183
    {
1184
        $showFields = array(
1185
            'CanViewType'  => '',
1186
            'ViewerGroups' => 'hide',
1187
            'CanEditType'  => '',
1188
            'EditorGroups' => 'hide',
1189
        );
1190
        /** @var SiteTree $siteTree */
1191
        $siteTree = singleton('SiteTree');
1192
        $settingsFields = $siteTree->getSettingsFields();
1193
1194
        foreach ($showFields as $name => $extraCss) {
1195
            $compositeName = "Root.Settings.$name";
1196
            /** @var FormField $field */
1197
            if ($field = $settingsFields->fieldByName($compositeName)) {
1198
                $field->addExtraClass($extraCss);
1199
                $title = str_replace('page', 'document', $field->Title());
1200
                $field->setTitle($title);
1201
1202
                // Remove Inherited source option from DropdownField
1203
                if ($field instanceof DropdownField) {
1204
                    $options = $field->getSource();
1205
                    unset($options['Inherit']);
1206
                    $field->setSource($options);
1207
                }
1208
                $fields->push($field);
1209
            }
1210
        }
1211
1212
        $this->extend('updatePermissionsFields', $fields);
1213
    }
1214
1215
    public function onBeforeWrite()
1216
    {
1217
        parent::onBeforeWrite();
1218
1219
        if (isset($this->Embargo)) {
1220
            //set the embargo options from the OptionSetField created in the getCMSFields method
1221
            //do not write after clearing the embargo (write happens automatically)
1222
            $savedDate = $this->EmbargoedUntilDate;
1223
            $this->clearEmbargo(false); //clear all previous settings and re-apply them on save
1224
1225
            if ($this->Embargo == 'Published') {
0 ignored issues
show
Bug introduced by
The property Embargo does not seem to exist. Did you mean EmbargoedIndefinitely?

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...
1226
                $this->embargoUntilPublished(false);
1227
            }
1228
            if ($this->Embargo == 'Indefinitely') {
0 ignored issues
show
Bug introduced by
The property Embargo does not seem to exist. Did you mean EmbargoedIndefinitely?

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...
1229
                $this->embargoIndefinitely(false);
1230
            }
1231
            if ($this->Embargo == 'Date') {
0 ignored issues
show
Bug introduced by
The property Embargo does not seem to exist. Did you mean EmbargoedIndefinitely?

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...
1232
                $this->embargoUntilDate($savedDate, false);
0 ignored issues
show
Documentation introduced by
$savedDate is of type object<DateTime>|null, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1233
            }
1234
        }
1235
1236
        if (isset($this->Expiry)) {
1237
            if ($this->Expiry == 'Date') {
0 ignored issues
show
Documentation introduced by
The property Expiry does not exist on object<DMSDocument>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1238
                $this->expireAtDate($this->ExpireAtDate, false);
0 ignored issues
show
Documentation introduced by
$this->ExpireAtDate is of type object<DateTime>|null, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1239
            } else {
1240
                $this->clearExpiry(false);
1241
            } //clear all previous settings
1242
        }
1243
    }
1244
1245
    /**
1246
     * Return the relative URL of an icon for the file type, based on the
1247
     * {@link appCategory()} value.
1248
     *
1249
     * Images are searched for in "dms/images/app_icons/".
1250
     *
1251
     * @return string
1252
     */
1253
    public function Icon($ext)
1254
    {
1255
        if (!Director::fileExists(DMS_DIR."/images/app_icons/{$ext}_32.png")) {
1256
            $ext = File::get_app_category($ext);
1257
        }
1258
1259
        if (!Director::fileExists(DMS_DIR."/images/app_icons/{$ext}_32.png")) {
1260
            $ext = "generic";
1261
        }
1262
1263
        return DMS_DIR."/images/app_icons/{$ext}_32.png";
1264
    }
1265
1266
    /**
1267
     * Return the extension of the file associated with the document
1268
     *
1269
     * @return string
1270
     */
1271
    public function getExtension()
1272
    {
1273
        return strtolower(pathinfo($this->Filename, PATHINFO_EXTENSION));
1274
    }
1275
1276
    /**
1277
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|false?

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...
1278
     */
1279
    public function getSize()
1280
    {
1281
        $size = $this->getAbsoluteSize();
1282
        return ($size) ? File::format_size($size) : false;
1283
    }
1284
1285
    /**
1286
     * Return the size of the file associated with the document.
1287
     *
1288
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|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...
1289
     */
1290
    public function getAbsoluteSize()
1291
    {
1292
        return file_exists($this->getFullPath()) ? filesize($this->getFullPath()) : null;
1293
    }
1294
1295
    /**
1296
     * An alias to DMSDocument::getSize()
1297
     *
1298
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|false?

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...
1299
     */
1300
    public function getFileSizeFormatted()
1301
    {
1302
        return $this->getSize();
1303
    }
1304
1305
1306
    /**
1307
     * @return FieldList
0 ignored issues
show
Documentation introduced by
Should the return type not be FieldGroup?

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...
1308
     */
1309
    protected function getFieldsForFile($relationListCount)
1310
    {
1311
        $extension = $this->getExtension();
1312
1313
        $previewField = new LiteralField(
1314
            "ImageFull",
1315
            "<img id='thumbnailImage' class='thumbnail-preview' src='{$this->Icon($extension)}?r="
1316
            . rand(1, 100000) . "' alt='{$this->Title}' />\n"
1317
        );
1318
1319
        //count the number of pages this document is published on
1320
        $publishedOnCount = $this->Pages()->Count();
1321
        $publishedOnValue = "$publishedOnCount pages";
1322
        if ($publishedOnCount == 1) {
1323
            $publishedOnValue = "$publishedOnCount page";
1324
        }
1325
1326
        $relationListCountValue = "$relationListCount pages";
1327
        if ($relationListCount == 1) {
1328
            $relationListCountValue = "$relationListCount page";
1329
        }
1330
1331
        $fields = new FieldGroup(
1332
            $filePreview = CompositeField::create(
1333
                CompositeField::create(
1334
                    $previewField
1335
                )->setName("FilePreviewImage")->addExtraClass('cms-file-info-preview'),
1336
                CompositeField::create(
1337
                    CompositeField::create(
1338
                        new ReadonlyField("ID", "ID number". ':', $this->ID),
1339
                        new ReadonlyField(
1340
                            "FileType",
1341
                            _t('AssetTableField.TYPE', 'File type') . ':',
1342
                            self::get_file_type($extension)
1343
                        ),
1344
                        new ReadonlyField(
1345
                            "Size",
1346
                            _t('AssetTableField.SIZE', 'File size') . ':',
1347
                            $this->getFileSizeFormatted()
1348
                        ),
1349
                        $urlField = new ReadonlyField(
1350
                            'ClickableURL',
1351
                            _t('AssetTableField.URL', 'URL'),
1352
                            sprintf(
1353
                                '<a href="%s" target="_blank" class="file-url">%s</a>',
1354
                                $this->getLink(),
1355
                                $this->getLink()
1356
                            )
1357
                        ),
1358
                        new ReadonlyField("FilenameWithoutIDField", "Filename". ':', $this->getFilenameWithoutID()),
1359
                        new DateField_Disabled(
1360
                            "Created",
1361
                            _t('AssetTableField.CREATED', 'First uploaded') . ':',
1362
                            $this->Created
1363
                        ),
1364
                        new DateField_Disabled(
1365
                            "LastEdited",
1366
                            _t('AssetTableField.LASTEDIT', 'Last changed') . ':',
1367
                            $this->LastEdited
1368
                        ),
1369
                        new DateField_Disabled(
1370
                            "LastChanged",
1371
                            _t('AssetTableField.LASTCHANGED', 'Last replaced') . ':',
1372
                            $this->LastChanged
1373
                        ),
1374
                        new ReadonlyField("PublishedOn", "Published on". ':', $publishedOnValue),
1375
                        new ReadonlyField("ReferencedOn", "Referenced on". ':', $relationListCountValue),
1376
                        new ReadonlyField("ViewCount", "View count". ':', $this->ViewCount)
1377
                    )
1378
                )->setName("FilePreviewData")->addExtraClass('cms-file-info-data')
1379
            )->setName("FilePreview")->addExtraClass('cms-file-info')
1380
        );
1381
1382
        $fields->setName('FileP');
1383
        $urlField->dontEscape = true;
1384
1385
        return $fields;
1386
    }
1387
1388
    /**
1389
     * Takes a file and adds it to the DMSDocument storage, replacing the
1390
     * current file.
1391
     *
1392
     * @param File $file
1393
     *
1394
     * @return $this
1395
     */
1396
    public function ingestFile($file)
1397
    {
1398
        $this->replaceDocument($file);
1399
        $file->delete();
1400
1401
        return $this;
1402
    }
1403
1404
    /**
1405
     * Get a data list of documents related to this document
1406
     *
1407
     * @return DataList
1408
     */
1409
    public function getRelatedDocuments()
1410
    {
1411
        $documents = $this->RelatedDocuments();
0 ignored issues
show
Bug introduced by
The method RelatedDocuments() does not exist on DMSDocument. Did you maybe mean getRelatedDocuments()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1412
1413
        $this->extend('updateRelatedDocuments', $documents);
1414
1415
        return $documents;
1416
    }
1417
1418
    /**
1419
     * Get a GridField for managing related documents
1420
     *
1421
     * @return GridField
1422
     */
1423
    protected function getRelatedDocumentsGridField()
1424
    {
1425
        $gridField = GridField::create(
1426
            'RelatedDocuments',
1427
            _t('DMSDocument.RELATEDDOCUMENTS', 'Related Documents'),
1428
            $this->RelatedDocuments(),
0 ignored issues
show
Bug introduced by
The method RelatedDocuments() does not exist on DMSDocument. Did you maybe mean getRelatedDocuments()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1429
            new GridFieldConfig_RelationEditor
1430
        );
1431
1432
        $gridField->getConfig()->removeComponentsByType('GridFieldAddNewButton');
1433
        // Move the autocompleter to the left
1434
        $gridField->getConfig()->removeComponentsByType('GridFieldAddExistingAutocompleter');
1435
        $gridField->getConfig()->addComponent(new GridFieldAddExistingAutocompleter('buttons-before-left'));
1436
1437
        $this->extend('updateRelatedDocumentsGridField', $gridField);
1438
1439
        return $gridField;
1440
    }
1441
1442
    /**
1443
     * Checks at least one group is selected if CanViewType || CanEditType == 'OnlyTheseUsers'
1444
     *
1445
     * @return ValidationResult
1446
     */
1447
    protected function validate()
1448
    {
1449
        $valid = parent::validate();
1450
1451
        if ($this->CanViewType == 'OnlyTheseUsers' && !$this->ViewerGroups()->count()) {
0 ignored issues
show
Documentation Bug introduced by
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...
1452
            $valid->error(
1453
                _t(
1454
                    'DMSDocument.VALIDATIONERROR_NOVIEWERSELECTED',
1455
                    "Selecting 'Only these people' from a viewers list needs at least one group selected."
1456
                )
1457
            );
1458
        }
1459
1460
        if ($this->CanEditType == 'OnlyTheseUsers' && !$this->EditorGroups()->count()) {
0 ignored issues
show
Documentation Bug introduced by
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...
1461
            $valid->error(
1462
                _t(
1463
                    'DMSDocument.VALIDATIONERROR_NOEDITORSELECTED',
1464
                    "Selecting 'Only these people' from a editors list needs at least one group selected."
1465
                )
1466
            );
1467
        }
1468
1469
        return $valid;
1470
    }
1471
1472
    /**
1473
     * Returns a reason as to why this document cannot be viewed.
1474
     *
1475
     * @return string
1476
     */
1477
    public function getPermissionDeniedReason()
1478
    {
1479
        $result = '';
1480
1481
        if ($this->CanViewType == 'LoggedInUsers') {
1482
            $result = _t('DMSDocument.PERMISSIONDENIEDREASON_LOGINREQUIRED', 'Please log in to view this document');
1483
        }
1484
1485
        if ($this->CanViewType == 'OnlyTheseUsers') {
1486
            $result = _t('DMSDocument.PERMISSIONDENIEDREASON_NOTAUTHORISED',
1487
                'You are not authorised to view this document');
1488
        }
1489
1490
        return $result;
1491
    }
1492
}
1493