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

DMSDocument::validate()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 8.5125
c 0
b 0
f 0
cc 5
eloc 13
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 View Code Duplication
    public function canCreate($member = null)
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...
182
    {
183
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
184
            $member = Member::currentUser();
185
        }
186
187
        $results = $this->extend('canCreate', $member);
188
189
        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...
190
            if (!min($results)) {
191
                return false;
192
            }
193
        }
194
195
        return $this->canEdit($member);
196
    }
197
198
    /**
199
     * @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...
200
     *
201
     * @return boolean
202
     */
203 View Code Duplication
    public function canDelete($member = null)
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...
204
    {
205
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
206
            $member = Member::currentUser();
207
        }
208
209
        $results = $this->extend('canDelete', $member);
210
211
        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...
212
            if (!min($results)) {
213
                return false;
214
            }
215
        }
216
217
        return $this->canView();
218
    }
219
220
221
222
    /**
223
     * Associates this document with a Page. This method does nothing if the
224
     * association already exists.
225
     *
226
     * This could be a simple wrapper around $myDoc->Pages()->add($myPage) to
227
     * add a many_many relation.
228
     *
229
     * @param SiteTree $pageObject Page object to associate this Document with
230
     *
231
     * @return DMSDocument
232
     */
233
    public function addPage($pageObject)
234
    {
235
        $this->Pages()->add($pageObject);
236
237
        DB::query(
238
            "UPDATE \"DMSDocument_Pages\" SET \"DocumentSort\"=\"DocumentSort\"+1"
239
            . " WHERE \"SiteTreeID\" = $pageObject->ID"
240
        );
241
242
        return $this;
243
    }
244
245
    /**
246
     * Associates this DMSDocument with a set of Pages. This method loops
247
     * through a set of page ids, and then associates this DMSDocument with the
248
     * individual Page with the each page id in the set.
249
     *
250
     * @param array $pageIDs
251
     *
252
     * @return DMSDocument
253
     */
254
    public function addPages($pageIDs)
255
    {
256
        foreach ($pageIDs as $id) {
257
            $pageObject = DataObject::get_by_id("SiteTree", $id);
258
259
            if ($pageObject && $pageObject->exists()) {
260
                $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...
261
            }
262
        }
263
264
        return $this;
265
    }
266
267
    /**
268
     * Removes the association between this Document and a Page. This method
269
     * does nothing if the association does not exist.
270
     *
271
     * @param SiteTree $pageObject Page object to remove the association to
272
     *
273
     * @return DMSDocument
274
     */
275
    public function removePage($pageObject)
276
    {
277
        $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...
278
279
        return $this;
280
    }
281
282
    /**
283
     * @see getPages()
284
     *
285
     * @return DataList
286
     */
287
    public function Pages()
288
    {
289
        $pages = $this->getManyManyComponents('Pages');
290
        $this->extend('updatePages', $pages);
291
292
        return $pages;
293
    }
294
295
    /**
296
     * Returns a list of the Page objects associated with this Document.
297
     *
298
     * @return DataList
299
     */
300
    public function getPages()
301
    {
302
        return $this->Pages();
303
    }
304
305
    /**
306
     * Removes all associated Pages from the DMSDocument
307
     *
308
     * @return DMSDocument
309
     */
310
    public function removeAllPages()
311
    {
312
        $this->Pages()->removeAll();
313
314
        return $this;
315
    }
316
317
    /**
318
     * Increase ViewCount by 1, without update any other record fields such as
319
     * LastEdited.
320
     *
321
     * @return DMSDocument
322
     */
323
    public function trackView()
324
    {
325
        if ($this->ID > 0) {
326
            $count = $this->ViewCount + 1;
327
328
            $this->ViewCount = $count;
329
330
            DB::query("UPDATE \"DMSDocument\" SET \"ViewCount\"='$count' WHERE \"ID\"={$this->ID}");
331
        }
332
333
        return $this;
334
    }
335
336
337
    /**
338
     * Adds a metadata tag to the Document. The tag has a category and a value.
339
     *
340
     * Each category can have multiple values by default. So:
341
     * addTag("fruit","banana") addTag("fruit", "apple") will add two items.
342
     *
343
     * However, if the third parameter $multiValue is set to 'false', then all
344
     * updates to a category only ever update a single value. So:
345
     * addTag("fruit","banana") addTag("fruit", "apple") would result in a
346
     * single metadata tag: fruit->apple.
347
     *
348
     * Can could be implemented as a key/value store table (although it is more
349
     * like category/value, because the same category can occur multiple times)
350
     *
351
     * @param string $category of a metadata category to add (required)
352
     * @param string $value of a metadata value to add (required)
353
     * @param bool $multiValue Boolean that determines if the category is
354
     *                  multi-value or single-value (optional)
355
     *
356
     * @return DMSDocument
357
     */
358
    public function addTag($category, $value, $multiValue = true)
359
    {
360
        if ($multiValue) {
361
            //check for a duplicate tag, don't add the duplicate
362
            $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...
363
            if ($currentTag->Count() == 0) {
364
                //multi value tag
365
                $tag = new DMSTag();
366
                $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...
367
                $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...
368
                $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...
369
                $tag->write();
370
                $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...
371
            } else {
372
                //add the relation between the tag and document
373
                foreach ($currentTag as $tagObj) {
374
                    $tagObj->Documents()->add($this);
375
                }
376
            }
377
        } else {
378
            //single value tag
379
            $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...
380
            $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...
381
            if ($currentTag->Count() == 0) {
382
                //create the single-value tag
383
                $tag = new DMSTag();
384
                $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...
385
                $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...
386
                $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...
387
                $tag->write();
388
            } else {
389
                //update the single value tag
390
                $tag = $currentTag->first();
391
                $tag->Value = $value;
392
                $tag->MultiValue = false;
393
                $tag->write();
394
            }
395
396
            // regardless of whether we created a new tag or are just updating an
397
            // existing one, add the relation
398
            $tag->Documents()->add($this);
399
        }
400
401
        return $this;
402
    }
403
404
    /**
405
     * @param string $category
406
     * @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...
407
     *
408
     * @return DataList
409
     */
410
    protected function getTagsObjects($category, $value = null)
411
    {
412
        $valueFilter = array("Category" => $category);
413
        if (!empty($value)) {
414
            $valueFilter['Value'] = $value;
415
        }
416
417
        $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...
418
        return $tags;
419
    }
420
421
    /**
422
     * Fetches all tags associated with this DMSDocument within a given
423
     * category. If a value is specified this method tries to fetch that
424
     * specific tag.
425
     *
426
     * @param string $category metadata category to get
427
     * @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...
428
     *
429
     * @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...
430
     */
431
    public function getTagsList($category, $value = null)
432
    {
433
        $tags = $this->getTagsObjects($category, $value);
434
435
        $returnArray = null;
436
437
        if ($tags->Count() > 0) {
438
            $returnArray = array();
439
440
            foreach ($tags as $t) {
441
                $returnArray[] = $t->Value;
442
            }
443
        }
444
445
        return $returnArray;
446
    }
447
448
    /**
449
     * Removes a tag from the Document. If you only set a category, then all
450
     * values in that category are deleted.
451
     *
452
     * If you specify both a category and a value, then only that single
453
     * category/value pair is deleted.
454
     *
455
     * Nothing happens if the category or the value do not exist.
456
     *
457
     * @param string $category Category to remove
458
     * @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...
459
     *
460
     * @return DMSDocument
461
     */
462
    public function removeTag($category, $value = null)
463
    {
464
        $tags = $this->getTagsObjects($category, $value);
465
466
        if ($tags->Count() > 0) {
467
            foreach ($tags as $t) {
468
                $documentList = $t->Documents();
469
470
                //remove the relation between the tag and the document
471
                $documentList->remove($this);
472
473
                //delete the entire tag if it has no relations left
474
                if ($documentList->Count() == 0) {
475
                    $t->delete();
476
                }
477
            }
478
        }
479
480
        return $this;
481
    }
482
483
    /**
484
     * Deletes all tags associated with this Document.
485
     *
486
     * @return DMSDocument
487
     */
488
    public function removeAllTags()
489
    {
490
        $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...
491
492
        foreach ($allTags as $tag) {
493
            $documentlist = $tag->Documents();
494
            $documentlist->remove($this);
495
            if ($tag->Documents()->Count() == 0) {
496
                $tag->delete();
497
            }
498
        }
499
500
        return $this;
501
    }
502
503
    /**
504
     * Returns a link to download this document from the DMS store.
505
     * Alternatively a basic javascript alert will be shown should the user not have view permissions. An extension
506
     * point for this was also added.
507
     *
508
     * To extend use the following from within an Extension subclass:
509
     *
510
     * <code>
511
     * public function updateGetLink($result)
512
     * {
513
     *     // Do something here
514
     * }
515
     * </code>
516
     *
517
     * @return string
518
     */
519
    public function getLink()
520
    {
521
        $result = Controller::join_links(Director::baseURL(), 'dmsdocument/' . $this->ID);
522
        if (!$this->canView()) {
523
            $result = sprintf("javascript:alert('%s')", $this->getPermissionDeniedReason());
524
        }
525
526
        $this->extend('updateGetLink', $result);
527
528
        return $result;
529
    }
530
531
    /**
532
     * @return string
533
     */
534
    public function Link()
535
    {
536
        return $this->getLink();
537
    }
538
539
    /**
540
     * Hides the document, so it does not show up when getByPage($myPage) is
541
     * called (without specifying the $showEmbargoed = true parameter).
542
     *
543
     * This is similar to expire, except that this method should be used to hide
544
     * documents that have not yet gone live.
545
     *
546
     * @param bool $write Save change to the database
547
     *
548
     * @return DMSDocument
549
     */
550
    public function embargoIndefinitely($write = true)
551
    {
552
        $this->EmbargoedIndefinitely = true;
553
554
        if ($write) {
555
            $this->write();
556
        }
557
558
        return $this;
559
    }
560
561
    /**
562
     * Hides the document until any page it is linked to is published
563
     *
564
     * @param bool $write Save change to database
565
     *
566
     * @return DMSDocument
567
     */
568
    public function embargoUntilPublished($write = true)
569
    {
570
        $this->EmbargoedUntilPublished = true;
571
572
        if ($write) {
573
            $this->write();
574
        }
575
576
        return $this;
577
    }
578
579
    /**
580
     * Returns if this is Document is embargoed or expired.
581
     *
582
     * Also, returns if the document should be displayed on the front-end,
583
     * respecting the current reading mode of the site and the embargo status.
584
     *
585
     * I.e. if a document is embargoed until published, then it should still
586
     * show up in draft mode.
587
     *
588
     * @return bool
589
     */
590
    public function isHidden()
591
    {
592
        $hidden = $this->isEmbargoed() || $this->isExpired();
593
        $readingMode = Versioned::get_reading_mode();
594
595
        if ($readingMode == "Stage.Stage") {
596
            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...
597
                $hidden = false;
598
            }
599
        }
600
601
        return $hidden;
602
    }
603
604
    /**
605
     * Returns if this is Document is embargoed.
606
     *
607
     * @return bool
608
     */
609
    public function isEmbargoed()
610
    {
611
        if (is_object($this->EmbargoedUntilDate)) {
612
            $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...
613
        }
614
615
        $embargoed = false;
616
617
        if ($this->EmbargoedIndefinitely) {
618
            $embargoed = true;
619
        } elseif ($this->EmbargoedUntilPublished) {
620
            $embargoed = true;
621
        } elseif (!empty($this->EmbargoedUntilDate)) {
622
            if (SS_Datetime::now()->Value < $this->EmbargoedUntilDate) {
623
                $embargoed = true;
624
            }
625
        }
626
627
        return $embargoed;
628
    }
629
630
    /**
631
     * Hides the document, so it does not show up when getByPage($myPage) is
632
     * called. Automatically un-hides the Document at a specific date.
633
     *
634
     * @param string $datetime date time value when this Document should expire.
635
     * @param bool $write
636
     *
637
     * @return DMSDocument
638
     */
639 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...
640
    {
641
        $this->EmbargoedUntilDate = DBField::create_field('SS_Datetime', $datetime)->Format('Y-m-d H:i:s');
642
643
        if ($write) {
644
            $this->write();
645
        }
646
647
        return $this;
648
    }
649
650
    /**
651
     * Clears any previously set embargos, so the Document always shows up in
652
     * all queries.
653
     *
654
     * @param bool $write
655
     *
656
     * @return DMSDocument
657
     */
658
    public function clearEmbargo($write = true)
659
    {
660
        $this->EmbargoedIndefinitely = false;
661
        $this->EmbargoedUntilPublished = false;
662
        $this->EmbargoedUntilDate = null;
663
664
        if ($write) {
665
            $this->write();
666
        }
667
668
        return $this;
669
    }
670
671
    /**
672
     * Returns if this is Document is expired.
673
     *
674
     * @return bool
675
     */
676
    public function isExpired()
677
    {
678
        if (is_object($this->ExpireAtDate)) {
679
            $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...
680
        }
681
682
        $expired = false;
683
684
        if (!empty($this->ExpireAtDate)) {
685
            if (SS_Datetime::now()->Value >= $this->ExpireAtDate) {
686
                $expired = true;
687
            }
688
        }
689
690
        return $expired;
691
    }
692
693
    /**
694
     * Hides the document at a specific date, so it does not show up when
695
     * getByPage($myPage) is called.
696
     *
697
     * @param string $datetime date time value when this Document should expire
698
     * @param bool $write
699
     *
700
     * @return DMSDocument
701
     */
702 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...
703
    {
704
        $this->ExpireAtDate = DBField::create_field('SS_Datetime', $datetime)->Format('Y-m-d H:i:s');
705
706
        if ($write) {
707
            $this->write();
708
        }
709
710
        return $this;
711
    }
712
713
    /**
714
     * Clears any previously set expiry.
715
     *
716
     * @param bool $write
717
     *
718
     * @return DMSDocument
719
     */
720
    public function clearExpiry($write = true)
721
    {
722
        $this->ExpireAtDate = null;
723
724
        if ($write) {
725
            $this->write();
726
        }
727
728
        return $this;
729
    }
730
731
    /**
732
     * Returns a DataList of all previous Versions of this document (check the
733
     * LastEdited date of each object to find the correct one).
734
     *
735
     * If {@link DMSDocument_versions::$enable_versions} is disabled then an
736
     * Exception is thrown
737
     *
738
     * @throws Exception
739
     *
740
     * @return DataList List of Document objects
741
     */
742
    public function getVersions()
743
    {
744
        if (!DMSDocument_versions::$enable_versions) {
745
            throw new Exception("DMSDocument versions are disabled");
746
        }
747
748
        return DMSDocument_versions::get_versions($this);
749
    }
750
751
    /**
752
     * Returns the full filename of the document stored in this object.
753
     *
754
     * @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...
755
     */
756
    public function getFullPath()
757
    {
758
        if ($this->Filename) {
759
            return DMS::get_dms_path() . DIRECTORY_SEPARATOR . $this->Folder . DIRECTORY_SEPARATOR . $this->Filename;
760
        }
761
762
        return null;
763
    }
764
765
    /**
766
     * Returns the filename of this asset.
767
     *
768
     * @return string
769
     */
770
    public function getFileName()
771
    {
772
        if ($this->getField('Filename')) {
773
            return $this->getField('Filename');
774
        } else {
775
            return ASSETS_DIR . '/';
776
        }
777
    }
778
779
    /**
780
     * @return string
781
     */
782
    public function getName()
783
    {
784
        return $this->getField('Title');
785
    }
786
787
788
    /**
789
     * @return string
790
     */
791
    public function getFilenameWithoutID()
792
    {
793
        $filenameParts = explode('~', $this->Filename);
794
        $filename = array_pop($filenameParts);
795
796
        return $filename;
797
    }
798
799
    /**
800
     * @return string
801
     */
802
    public function getStorageFolder()
803
    {
804
        return DMS::get_dms_path() . DIRECTORY_SEPARATOR . DMS::get_storage_folder($this->ID);
805
    }
806
807
    /**
808
     * Deletes the DMSDocument, its underlying file, as well as any tags related
809
     * to this DMSDocument. Also calls the parent DataObject's delete method in
810
     * order to complete an cascade.
811
     *
812
     * @return void
813
     */
814
    public function delete()
815
    {
816
        // remove tags
817
        $this->removeAllTags();
818
819
        // delete the file (and previous versions of files)
820
        $filesToDelete = array();
821
        $storageFolder = $this->getStorageFolder();
822
823
        if (file_exists($storageFolder)) {
824
            if ($handle = opendir($storageFolder)) {
825
                while (false !== ($entry = readdir($handle))) {
826
                    // only delete if filename starts the the relevant ID
827
                    if (strpos($entry, $this->ID.'~') === 0) {
828
                        $filesToDelete[] = $entry;
829
                    }
830
                }
831
832
                closedir($handle);
833
834
                //delete all this files that have the id of this document
835
                foreach ($filesToDelete as $file) {
836
                    $filePath = $storageFolder .DIRECTORY_SEPARATOR . $file;
837
838
                    if (is_file($filePath)) {
839
                        unlink($filePath);
840
                    }
841
                }
842
            }
843
        }
844
845
        $this->removeAllPages();
846
847
        // get rid of any versions have saved for this DMSDocument, too
848
        if (DMSDocument_versions::$enable_versions) {
849
            $versions = $this->getVersions();
850
851
            if ($versions->Count() > 0) {
852
                foreach ($versions as $v) {
853
                    $v->delete();
854
                }
855
            }
856
        }
857
858
        parent::delete();
859
    }
860
861
862
863
    /**
864
     * Relate an existing file on the filesystem to the document.
865
     *
866
     * Copies the file to the new destination, as defined in {@link get_DMS_path()}.
867
     *
868
     * @param string $filePath Path to file, relative to webroot.
869
     *
870
     * @return DMSDocument
871
     */
872
    public function storeDocument($filePath)
873
    {
874
        if (empty($this->ID)) {
875
            user_error("Document must be written to database before it can store documents", E_USER_ERROR);
876
        }
877
878
        // calculate all the path to copy the file to
879
        $fromFilename = basename($filePath);
880
        $toFilename = $this->ID. '~' . $fromFilename; //add the docID to the start of the Filename
881
        $toFolder = DMS::get_storage_folder($this->ID);
882
        $toPath = DMS::get_dms_path() . DIRECTORY_SEPARATOR . $toFolder . DIRECTORY_SEPARATOR . $toFilename;
883
884
        DMS::create_storage_folder(DMS::get_dms_path() . DIRECTORY_SEPARATOR . $toFolder);
885
886
        //copy the file into place
887
        $fromPath = BASE_PATH . DIRECTORY_SEPARATOR . $filePath;
888
889
        //version the existing file (copy it to a new "very specific" filename
890
        if (DMSDocument_versions::$enable_versions) {
891
            DMSDocument_versions::create_version($this);
892
        } else {    //otherwise delete the old document file
893
            $oldPath = $this->getFullPath();
894
            if (file_exists($oldPath)) {
895
                unlink($oldPath);
896
            }
897
        }
898
899
        copy($fromPath, $toPath);   //this will overwrite the existing file (if present)
900
901
        //write the filename of the stored document
902
        $this->Filename = $toFilename;
903
        $this->Folder = strval($toFolder);
904
905
        $extension = pathinfo($this->Filename, PATHINFO_EXTENSION);
906
907
        if (empty($this->Title)) {
908
            // don't overwrite existing document titles
909
            $this->Title = basename($filePath, '.'.$extension);
910
        }
911
912
        $this->LastChanged = SS_Datetime::now()->Rfc2822();
913
        $this->write();
914
915
        return $this;
916
    }
917
918
    /**
919
     * Takes a File object or a String (path to a file) and copies it into the
920
     * DMS, replacing the original document file but keeping the rest of the
921
     * document unchanged.
922
     *
923
     * @param File|string $file path to a file to store
924
     *
925
     * @return DMSDocument object that we replaced the file in
926
     */
927
    public function replaceDocument($file)
928
    {
929
        $filePath = DMS::transform_file_to_file_path($file);
930
        $doc = $this->storeDocument($filePath); // replace the document
931
932
        return $doc;
933
    }
934
935
936
    /**
937
     * Return the type of file for the given extension
938
     * on the current file name.
939
     *
940
     * @param string $ext
941
     *
942
     * @return string
943
     */
944
    public static function get_file_type($ext)
945
    {
946
        $types = array(
947
            'gif' => 'GIF image - good for diagrams',
948
            'jpg' => 'JPEG image - good for photos',
949
            'jpeg' => 'JPEG image - good for photos',
950
            'png' => 'PNG image - good general-purpose format',
951
            'ico' => 'Icon image',
952
            'tiff' => 'Tagged image format',
953
            'doc' => 'Word document',
954
            'xls' => 'Excel spreadsheet',
955
            'zip' => 'ZIP compressed file',
956
            'gz' => 'GZIP compressed file',
957
            'dmg' => 'Apple disk image',
958
            'pdf' => 'Adobe Acrobat PDF file',
959
            'mp3' => 'MP3 audio file',
960
            'wav' => 'WAV audo file',
961
            'avi' => 'AVI video file',
962
            'mpg' => 'MPEG video file',
963
            'mpeg' => 'MPEG video file',
964
            'js' => 'Javascript file',
965
            'css' => 'CSS file',
966
            'html' => 'HTML file',
967
            'htm' => 'HTML file'
968
        );
969
970
        return isset($types[$ext]) ? $types[$ext] : $ext;
971
    }
972
973
974
    /**
975
     * Returns the Description field with HTML <br> tags added when there is a
976
     * line break.
977
     *
978
     * @return string
979
     */
980
    public function getDescriptionWithLineBreak()
981
    {
982
        return nl2br($this->getField('Description'));
983
    }
984
985
    /**
986
     * @return FieldList
987
     */
988
    public function getCMSFields()
989
    {
990
        //include JS to handling showing and hiding of bottom "action" tabs
991
        Requirements::javascript(DMS_DIR.'/javascript/DMSDocumentCMSFields.js');
992
        Requirements::css(DMS_DIR.'/css/DMSDocumentCMSFields.css');
993
994
        $fields = new FieldList();  //don't use the automatic scaffolding, it is slow and unnecessary here
995
996
        $extraTasks = '';   //additional text to inject into the list of tasks at the bottom of a DMSDocument CMSfield
997
998
        //get list of shortcode page relations
999
        $relationFinder = new ShortCodeRelationFinder();
1000
        $relationList = $relationFinder->getList($this->ID);
1001
1002
        $fieldsTop = $this->getFieldsForFile($relationList->count());
1003
        $fields->add($fieldsTop);
1004
1005
        $fields->add(new TextField('Title', 'Title'));
1006
        $fields->add(new TextareaField('Description', 'Description'));
1007
1008
        $downloadBehaviorSource = array(
1009
            'open' => _t('DMSDocument.OPENINBROWSER', 'Open in browser'),
1010
            'download' => _t('DMSDocument.FORCEDOWNLOAD', 'Force download'),
1011
        );
1012
        $defaultDownloadBehaviour = Config::inst()->get('DMSDocument', 'default_download_behaviour');
1013
        if (!isset($downloadBehaviorSource[$defaultDownloadBehaviour])) {
1014
            user_error('Default download behaviour "' . $defaultDownloadBehaviour . '" not supported.', E_USER_WARNING);
1015
        } else {
1016
            $downloadBehaviorSource[$defaultDownloadBehaviour] .= ' (' . _t('DMSDocument.DEFAULT', 'default') . ')';
1017
        }
1018
1019
        $fields->add(
1020
            OptionsetField::create(
1021
                'DownloadBehavior',
1022
                _t('DMSDocument.DOWNLOADBEHAVIOUR', 'Download behavior'),
1023
                $downloadBehaviorSource,
1024
                $defaultDownloadBehaviour
1025
            )
1026
            ->setDescription(
1027
                'How the visitor will view this file. <strong>Open in browser</strong> '
1028
                . 'allows files to be opened in a new tab.'
1029
            )
1030
        );
1031
1032
        //create upload field to replace document
1033
        $uploadField = new DMSUploadField('ReplaceFile', 'Replace file');
1034
        $uploadField->setConfig('allowedMaxFileNumber', 1);
1035
        $uploadField->setConfig('downloadTemplateName', 'ss-dmsuploadfield-downloadtemplate');
1036
        $uploadField->setRecord($this);
1037
1038
        $gridFieldConfig = GridFieldConfig::create()->addComponents(
1039
            new GridFieldToolbarHeader(),
1040
            new GridFieldSortableHeader(),
1041
            new GridFieldDataColumns(),
1042
            new GridFieldPaginator(30),
1043
            //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...
1044
            new GridFieldDetailForm()
1045
        );
1046
1047
        $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...
1048
            ->setDisplayFields(array(
1049
                'Title'=>'Title',
1050
                'ClassName'=>'Page Type',
1051
                'ID'=>'Page ID'
1052
            ))
1053
            ->setFieldFormatting(array(
1054
                'Title'=>sprintf(
1055
                    '<a class=\"cms-panel-link\" href=\"%s/$ID\">$Title</a>',
1056
                    singleton('CMSPageEditController')->Link('show')
1057
                )
1058
            ));
1059
1060
        $pagesGrid = GridField::create(
1061
            'Pages',
1062
            _t('DMSDocument.RelatedPages', 'Related Pages'),
1063
            $this->Pages(),
1064
            $gridFieldConfig
1065
        );
1066
1067
        $referencesGrid = GridField::create(
1068
            'References',
1069
            _t('DMSDocument.RelatedReferences', 'Related References'),
1070
            $relationList,
1071
            $gridFieldConfig
1072
        );
1073
1074
        if (DMSDocument_versions::$enable_versions) {
1075
            $versionsGridFieldConfig = GridFieldConfig::create()->addComponents(
1076
                new GridFieldToolbarHeader(),
1077
                new GridFieldSortableHeader(),
1078
                new GridFieldDataColumns(),
1079
                new GridFieldPaginator(30)
1080
            );
1081
            $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...
1082
                ->setDisplayFields(Config::inst()->get('DMSDocument_versions', 'display_fields'))
1083
                ->setFieldCasting(array('LastChanged'=>"Datetime->Ago"))
1084
                ->setFieldFormatting(
1085
                    array(
1086
                        'FilenameWithoutID' => '<a target=\'_blank\' class=\'file-url\' href=\'$Link\'>'
1087
                            . '$FilenameWithoutID</a>'
1088
                    )
1089
                );
1090
1091
            $versionsGrid =  GridField::create(
1092
                'Versions',
1093
                _t('DMSDocument.Versions', 'Versions'),
1094
                $this->getVersions(),
1095
                $versionsGridFieldConfig
1096
            );
1097
            $extraTasks .= '<li class="ss-ui-button" data-panel="find-versions">Versions</li>';
1098
        }
1099
1100
        $fields->add(new LiteralField(
1101
            'BottomTaskSelection',
1102
            '<div id="Actions" class="field actions"><label class="left">Actions</label><ul>'
1103
            . '<li class="ss-ui-button" data-panel="embargo">Embargo</li>'
1104
            . '<li class="ss-ui-button" data-panel="expiry">Expiry</li>'
1105
            . '<li class="ss-ui-button" data-panel="replace">Replace</li>'
1106
            . '<li class="ss-ui-button" data-panel="find-usage">Usage</li>'
1107
            . '<li class="ss-ui-button" data-panel="find-references">References</li>'
1108
            . '<li class="ss-ui-button" data-panel="find-relateddocuments">Related Documents</li>'
1109
            . $extraTasks
1110
            . '</ul></div>'
1111
        ));
1112
1113
        $embargoValue = 'None';
1114
        if ($this->EmbargoedIndefinitely) {
1115
            $embargoValue = 'Indefinitely';
1116
        } elseif ($this->EmbargoedUntilPublished) {
1117
            $embargoValue = 'Published';
1118
        } elseif (!empty($this->EmbargoedUntilDate)) {
1119
            $embargoValue = 'Date';
1120
        }
1121
        $embargo = new OptionsetField(
1122
            'Embargo',
1123
            'Embargo',
1124
            array(
1125
                'None' => 'None',
1126
                'Published' => 'Hide document until page is published',
1127
                'Indefinitely' => 'Hide document indefinitely',
1128
                'Date' => 'Hide until set date'
1129
            ),
1130
            $embargoValue
1131
        );
1132
        $embargoDatetime = DatetimeField::create('EmbargoedUntilDate', '');
1133
        $embargoDatetime->getDateField()
1134
            ->setConfig('showcalendar', true)
1135
            ->setConfig('dateformat', 'dd-MM-yyyy')
1136
            ->setConfig('datavalueformat', 'dd-MM-yyyy');
1137
1138
        $expiryValue = 'None';
1139
        if (!empty($this->ExpireAtDate)) {
1140
            $expiryValue = 'Date';
1141
        }
1142
        $expiry = new OptionsetField(
1143
            'Expiry',
1144
            'Expiry',
1145
            array(
1146
                'None' => 'None',
1147
                'Date' => 'Set document to expire on'
1148
            ),
1149
            $expiryValue
1150
        );
1151
        $expiryDatetime = DatetimeField::create('ExpireAtDate', '');
1152
        $expiryDatetime->getDateField()
1153
            ->setConfig('showcalendar', true)
1154
            ->setConfig('dateformat', 'dd-MM-yyyy')
1155
            ->setConfig('datavalueformat', 'dd-MM-yyyy');
1156
1157
        // This adds all the actions details into a group.
1158
        // Embargo, History, etc to go in here
1159
        // These are toggled on and off via the Actions Buttons above
1160
        // 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...
1161
        $actionsPanel = FieldGroup::create(
1162
            FieldGroup::create($embargo, $embargoDatetime)->addExtraClass('embargo'),
1163
            FieldGroup::create($expiry, $expiryDatetime)->addExtraClass('expiry'),
1164
            FieldGroup::create($uploadField)->addExtraClass('replace'),
1165
            FieldGroup::create($pagesGrid)->addExtraClass('find-usage'),
1166
            FieldGroup::create($referencesGrid)->addExtraClass('find-references'),
1167
            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...
1168
            FieldGroup::create($this->getRelatedDocumentsGridField())->addExtraClass('find-relateddocuments')
1169
        );
1170
1171
        $actionsPanel->setName("ActionsPanel");
1172
        $actionsPanel->addExtraClass("DMSDocumentActionsPanel");
1173
        $fields->push($actionsPanel);
1174
1175
        $this->addPermissionsFields($fields);
1176
        $this->extend('updateCMSFields', $fields);
1177
1178
        return $fields;
1179
    }
1180
1181
    /**
1182
     * Adds permissions selection fields to the FieldList.
1183
     *
1184
     * @param FieldList $fields
1185
     */
1186
    public function addPermissionsFields($fields)
1187
    {
1188
        $showFields = array(
1189
            'CanViewType'  => '',
1190
            'ViewerGroups' => 'hide',
1191
            'CanEditType'  => '',
1192
            'EditorGroups' => 'hide',
1193
        );
1194
        /** @var SiteTree $siteTree */
1195
        $siteTree = singleton('SiteTree');
1196
        $settingsFields = $siteTree->getSettingsFields();
1197
1198
        foreach ($showFields as $name => $extraCss) {
1199
            $compositeName = "Root.Settings.$name";
1200
            /** @var FormField $field */
1201
            if ($field = $settingsFields->fieldByName($compositeName)) {
1202
                $field->addExtraClass($extraCss);
1203
                $title = str_replace('page', 'document', $field->Title());
1204
                $field->setTitle($title);
1205
1206
                // Remove Inherited source option from DropdownField
1207
                if ($field instanceof DropdownField) {
1208
                    $options = $field->getSource();
1209
                    unset($options['Inherit']);
1210
                    $field->setSource($options);
1211
                }
1212
                $fields->push($field);
1213
            }
1214
        }
1215
1216
        $this->extend('updatePermissionsFields', $fields);
1217
    }
1218
1219
    public function onBeforeWrite()
1220
    {
1221
        parent::onBeforeWrite();
1222
1223
        if (isset($this->Embargo)) {
1224
            //set the embargo options from the OptionSetField created in the getCMSFields method
1225
            //do not write after clearing the embargo (write happens automatically)
1226
            $savedDate = $this->EmbargoedUntilDate;
1227
            $this->clearEmbargo(false); //clear all previous settings and re-apply them on save
1228
1229
            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...
1230
                $this->embargoUntilPublished(false);
1231
            }
1232
            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...
1233
                $this->embargoIndefinitely(false);
1234
            }
1235
            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...
1236
                $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...
1237
            }
1238
        }
1239
1240
        if (isset($this->Expiry)) {
1241
            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...
1242
                $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...
1243
            } else {
1244
                $this->clearExpiry(false);
1245
            } //clear all previous settings
1246
        }
1247
    }
1248
1249
    /**
1250
     * Return the relative URL of an icon for the file type, based on the
1251
     * {@link appCategory()} value.
1252
     *
1253
     * Images are searched for in "dms/images/app_icons/".
1254
     *
1255
     * @return string
1256
     */
1257
    public function Icon($ext)
1258
    {
1259
        if (!Director::fileExists(DMS_DIR."/images/app_icons/{$ext}_32.png")) {
1260
            $ext = File::get_app_category($ext);
1261
        }
1262
1263
        if (!Director::fileExists(DMS_DIR."/images/app_icons/{$ext}_32.png")) {
1264
            $ext = "generic";
1265
        }
1266
1267
        return DMS_DIR."/images/app_icons/{$ext}_32.png";
1268
    }
1269
1270
    /**
1271
     * Return the extension of the file associated with the document
1272
     *
1273
     * @return string
1274
     */
1275
    public function getExtension()
1276
    {
1277
        return strtolower(pathinfo($this->Filename, PATHINFO_EXTENSION));
1278
    }
1279
1280
    /**
1281
     * @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...
1282
     */
1283
    public function getSize()
1284
    {
1285
        $size = $this->getAbsoluteSize();
1286
        return ($size) ? File::format_size($size) : false;
1287
    }
1288
1289
    /**
1290
     * Return the size of the file associated with the document.
1291
     *
1292
     * @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...
1293
     */
1294
    public function getAbsoluteSize()
1295
    {
1296
        return file_exists($this->getFullPath()) ? filesize($this->getFullPath()) : null;
1297
    }
1298
1299
    /**
1300
     * An alias to DMSDocument::getSize()
1301
     *
1302
     * @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...
1303
     */
1304
    public function getFileSizeFormatted()
1305
    {
1306
        return $this->getSize();
1307
    }
1308
1309
1310
    /**
1311
     * @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...
1312
     */
1313
    protected function getFieldsForFile($relationListCount)
1314
    {
1315
        $extension = $this->getExtension();
1316
1317
        $previewField = new LiteralField(
1318
            "ImageFull",
1319
            "<img id='thumbnailImage' class='thumbnail-preview' src='{$this->Icon($extension)}?r="
1320
            . rand(1, 100000) . "' alt='{$this->Title}' />\n"
1321
        );
1322
1323
        //count the number of pages this document is published on
1324
        $publishedOnCount = $this->Pages()->Count();
1325
        $publishedOnValue = "$publishedOnCount pages";
1326
        if ($publishedOnCount == 1) {
1327
            $publishedOnValue = "$publishedOnCount page";
1328
        }
1329
1330
        $relationListCountValue = "$relationListCount pages";
1331
        if ($relationListCount == 1) {
1332
            $relationListCountValue = "$relationListCount page";
1333
        }
1334
1335
        $fields = new FieldGroup(
1336
            $filePreview = CompositeField::create(
1337
                CompositeField::create(
1338
                    $previewField
1339
                )->setName("FilePreviewImage")->addExtraClass('cms-file-info-preview'),
1340
                CompositeField::create(
1341
                    CompositeField::create(
1342
                        new ReadonlyField("ID", "ID number". ':', $this->ID),
1343
                        new ReadonlyField(
1344
                            "FileType",
1345
                            _t('AssetTableField.TYPE', 'File type') . ':',
1346
                            self::get_file_type($extension)
1347
                        ),
1348
                        new ReadonlyField(
1349
                            "Size",
1350
                            _t('AssetTableField.SIZE', 'File size') . ':',
1351
                            $this->getFileSizeFormatted()
1352
                        ),
1353
                        $urlField = new ReadonlyField(
1354
                            'ClickableURL',
1355
                            _t('AssetTableField.URL', 'URL'),
1356
                            sprintf(
1357
                                '<a href="%s" target="_blank" class="file-url">%s</a>',
1358
                                $this->getLink(),
1359
                                $this->getLink()
1360
                            )
1361
                        ),
1362
                        new ReadonlyField("FilenameWithoutIDField", "Filename". ':', $this->getFilenameWithoutID()),
1363
                        new DateField_Disabled(
1364
                            "Created",
1365
                            _t('AssetTableField.CREATED', 'First uploaded') . ':',
1366
                            $this->Created
1367
                        ),
1368
                        new DateField_Disabled(
1369
                            "LastEdited",
1370
                            _t('AssetTableField.LASTEDIT', 'Last changed') . ':',
1371
                            $this->LastEdited
1372
                        ),
1373
                        new DateField_Disabled(
1374
                            "LastChanged",
1375
                            _t('AssetTableField.LASTCHANGED', 'Last replaced') . ':',
1376
                            $this->LastChanged
1377
                        ),
1378
                        new ReadonlyField("PublishedOn", "Published on". ':', $publishedOnValue),
1379
                        new ReadonlyField("ReferencedOn", "Referenced on". ':', $relationListCountValue),
1380
                        new ReadonlyField("ViewCount", "View count". ':', $this->ViewCount)
1381
                    )
1382
                )->setName("FilePreviewData")->addExtraClass('cms-file-info-data')
1383
            )->setName("FilePreview")->addExtraClass('cms-file-info')
1384
        );
1385
1386
        $fields->setName('FileP');
1387
        $urlField->dontEscape = true;
1388
1389
        return $fields;
1390
    }
1391
1392
    /**
1393
     * Takes a file and adds it to the DMSDocument storage, replacing the
1394
     * current file.
1395
     *
1396
     * @param File $file
1397
     *
1398
     * @return $this
1399
     */
1400
    public function ingestFile($file)
1401
    {
1402
        $this->replaceDocument($file);
1403
        $file->delete();
1404
1405
        return $this;
1406
    }
1407
1408
    /**
1409
     * Get a data list of documents related to this document
1410
     *
1411
     * @return DataList
1412
     */
1413
    public function getRelatedDocuments()
1414
    {
1415
        $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...
1416
1417
        $this->extend('updateRelatedDocuments', $documents);
1418
1419
        return $documents;
1420
    }
1421
1422
    /**
1423
     * Get a GridField for managing related documents
1424
     *
1425
     * @return GridField
1426
     */
1427
    protected function getRelatedDocumentsGridField()
1428
    {
1429
        $gridField = GridField::create(
1430
            'RelatedDocuments',
1431
            _t('DMSDocument.RELATEDDOCUMENTS', 'Related Documents'),
1432
            $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...
1433
            new GridFieldConfig_RelationEditor
1434
        );
1435
1436
        $gridField->getConfig()->removeComponentsByType('GridFieldAddNewButton');
1437
        // Move the autocompleter to the left
1438
        $gridField->getConfig()->removeComponentsByType('GridFieldAddExistingAutocompleter');
1439
        $gridField->getConfig()->addComponent(new GridFieldAddExistingAutocompleter('buttons-before-left'));
1440
1441
        $this->extend('updateRelatedDocumentsGridField', $gridField);
1442
1443
        return $gridField;
1444
    }
1445
1446
    /**
1447
     * Checks at least one group is selected if CanViewType || CanEditType == 'OnlyTheseUsers'
1448
     *
1449
     * @return ValidationResult
1450
     */
1451
    protected function validate()
1452
    {
1453
        $valid = parent::validate();
1454
1455
        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...
1456
            $valid->error(
1457
                _t(
1458
                    'DMSDocument.VALIDATIONERROR_NOVIEWERSELECTED',
1459
                    "Selecting 'Only these people' from a viewers list needs at least one group selected."
1460
                )
1461
            );
1462
        }
1463
1464
        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...
1465
            $valid->error(
1466
                _t(
1467
                    'DMSDocument.VALIDATIONERROR_NOEDITORSELECTED',
1468
                    "Selecting 'Only these people' from a editors list needs at least one group selected."
1469
                )
1470
            );
1471
        }
1472
1473
        return $valid;
1474
    }
1475
1476
    /**
1477
     * Returns a reason as to why this document cannot be viewed.
1478
     *
1479
     * @return string
1480
     */
1481
    public function getPermissionDeniedReason()
1482
    {
1483
        $result = '';
1484
1485
        if ($this->CanViewType == 'LoggedInUsers') {
1486
            $result = _t('DMSDocument.PERMISSIONDENIEDREASON_LOGINREQUIRED', 'Please log in to view this document');
1487
        }
1488
1489
        if ($this->CanViewType == 'OnlyTheseUsers') {
1490
            $result = _t('DMSDocument.PERMISSIONDENIEDREASON_NOTAUTHORISED',
1491
                'You are not authorised to view this document');
1492
        }
1493
1494
        return $result;
1495
    }
1496
}
1497