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

DMSDocument::validate()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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