Completed
Push — master ( 6b80f3...f506fb )
by Franco
12s
created

DMSDocument::getTitle()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 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 $belongs_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 $belongs_many_many is not used and could be removed.

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

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

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

Loading history...
51
        'CoverImage' => 'Image'
52
    );
53
54
    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...
55
        'RelatedDocuments' => 'DMSDocument',
56
        'Tags' => 'DMSTag',
57
        'ViewerGroups' => 'Group',
58
        'EditorGroups' => 'Group',
59
    );
60
61
    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...
62
        'ID' => 'ID',
63
        'Title' => 'Title',
64
        'FilenameWithoutID' => 'Filename',
65
        'LastChanged' => 'LastChanged'
66
    );
67
68
    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...
69
70
    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...
71
72
    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...
73
        'ID' => array(
74
            'filter' => 'ExactMatchFilter',
75
            'field' => 'NumericField'
76
        ),
77
        'Title',
78
        'Filename',
79
        'LastChanged'
80
    );
81
82
    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...
83
        'Filename' => 'Filename',
84
        'Title' => 'Title',
85
        'ViewCount' => 'ViewCount',
86
        'getRelatedPages.count' => 'Page Use'
87
    );
88
89
    /**
90
     * @var string download|open
91
     * @config
92
     */
93
    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...
94
95
    /**
96
     * A key value map of the "actions" tabs that will be added to the CMS fields
97
     *
98
     * @var array
99
     */
100
    protected $actionTasks = array(
101
        'embargo' => 'Embargo',
102
        'expiry' => 'Expiry',
103
        'replace' => 'Replace',
104
        'find-usage' => 'Usage',
105
        'find-references' => 'References',
106
        'find-relateddocuments' => 'Related Documents',
107
        'permissions' => 'Permissions'
108
    );
109
110
    public function canView($member = null)
111
    {
112
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
113
            $member = Member::currentUser();
114
        }
115
116
        // extended access checks
117
        $results = $this->extend('canView', $member);
118
119
        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...
120
            if (!min($results)) {
121
                return false;
122
            }
123
        }
124
125
        if (!$this->CanViewType || $this->CanViewType == 'Anyone') {
126
            return true;
127
        }
128
129 View Code Duplication
        if ($member && Permission::checkMember($member, array('ADMIN', 'SITETREE_EDIT_ALL', 'SITETREE_VIEW_ALL'))) {
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...
130
            return true;
131
        }
132
133
        if ($this->isHidden()) {
134
            return false;
135
        }
136
137
        if ($this->CanViewType == 'LoggedInUsers') {
138
            return $member && $member->exists();
139
        }
140
141
        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...
142
            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...
143
        }
144
145
        return $this->canEdit($member);
146
    }
147
148
    public function canEdit($member = null)
149
    {
150
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
151
            $member = Member::currentUser();
152
        }
153
154
        $results = $this->extend('canEdit', $member);
155
156
        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...
157
            if (!min($results)) {
158
                return false;
159
            }
160
        }
161
162
        // Do early admin check
163 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...
164
                array(
165
                    'ADMIN',
166
                    'SITETREE_EDIT_ALL',
167
                    'SITETREE_VIEW_ALL',
168
                )
169
            )
170
        ) {
171
            return true;
172
        }
173
174
        if ($this->CanEditType === 'LoggedInUsers') {
175
            return $member && $member->exists();
176
        }
177
178
        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...
179
            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...
180
        }
181
182
        return ($member && Permission::checkMember($member, array('ADMIN', 'SITETREE_EDIT_ALL')));
183
    }
184
185
    /**
186
     * @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...
187
     *
188
     * @return boolean
189
     */
190 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...
191
    {
192
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
193
            $member = Member::currentUser();
194
        }
195
196
        $results = $this->extend('canCreate', $member);
197
198
        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...
199
            if (!min($results)) {
200
                return false;
201
            }
202
        }
203
204
        return $this->canEdit($member);
205
    }
206
207
    /**
208
     * @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...
209
     *
210
     * @return boolean
211
     */
212 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...
213
    {
214
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
215
            $member = Member::currentUser();
216
        }
217
218
        $results = $this->extend('canDelete', $member);
219
220
        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...
221
            if (!min($results)) {
222
                return false;
223
            }
224
        }
225
226
        return $this->canView();
227
    }
228
229
    /**
230
     * Increase ViewCount by 1, without update any other record fields such as
231
     * LastEdited.
232
     *
233
     * @return DMSDocument
234
     */
235
    public function trackView()
236
    {
237
        if ($this->ID > 0) {
238
            $count = $this->ViewCount + 1;
239
240
            $this->ViewCount = $count;
241
242
            DB::query("UPDATE \"DMSDocument\" SET \"ViewCount\"='$count' WHERE \"ID\"={$this->ID}");
243
        }
244
245
        return $this;
246
    }
247
248
249
    /**
250
     * Adds a metadata tag to the Document. The tag has a category and a value.
251
     *
252
     * Each category can have multiple values by default. So:
253
     * addTag("fruit","banana") addTag("fruit", "apple") will add two items.
254
     *
255
     * However, if the third parameter $multiValue is set to 'false', then all
256
     * updates to a category only ever update a single value. So:
257
     * addTag("fruit","banana") addTag("fruit", "apple") would result in a
258
     * single metadata tag: fruit->apple.
259
     *
260
     * Can could be implemented as a key/value store table (although it is more
261
     * like category/value, because the same category can occur multiple times)
262
     *
263
     * @param string $category of a metadata category to add (required)
264
     * @param string $value of a metadata value to add (required)
265
     * @param bool $multiValue Boolean that determines if the category is
266
     *                  multi-value or single-value (optional)
267
     *
268
     * @return DMSDocument
269
     */
270
    public function addTag($category, $value, $multiValue = true)
271
    {
272
        if ($multiValue) {
273
            //check for a duplicate tag, don't add the duplicate
274
            $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...
275
            if ($currentTag->Count() == 0) {
276
                //multi value tag
277
                $tag = new DMSTag();
278
                $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...
279
                $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...
280
                $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...
281
                $tag->write();
282
                $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...
283
            } else {
284
                //add the relation between the tag and document
285
                foreach ($currentTag as $tagObj) {
286
                    $tagObj->Documents()->add($this);
287
                }
288
            }
289
        } else {
290
            //single value tag
291
            $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...
292
            $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...
293
            if ($currentTag->Count() == 0) {
294
                //create the single-value tag
295
                $tag = new DMSTag();
296
                $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...
297
                $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...
298
                $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...
299
                $tag->write();
300
            } else {
301
                //update the single value tag
302
                $tag = $currentTag->first();
303
                $tag->Value = $value;
304
                $tag->MultiValue = false;
305
                $tag->write();
306
            }
307
308
            // regardless of whether we created a new tag or are just updating an
309
            // existing one, add the relation
310
            $tag->Documents()->add($this);
311
        }
312
313
        return $this;
314
    }
315
316
    /**
317
     * @param string $category
318
     * @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...
319
     *
320
     * @return DataList
321
     */
322
    protected function getTagsObjects($category, $value = null)
323
    {
324
        $valueFilter = array("Category" => $category);
325
        if (!empty($value)) {
326
            $valueFilter['Value'] = $value;
327
        }
328
329
        $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...
330
        return $tags;
331
    }
332
333
    /**
334
     * Fetches all tags associated with this DMSDocument within a given
335
     * category. If a value is specified this method tries to fetch that
336
     * specific tag.
337
     *
338
     * @param string $category metadata category to get
339
     * @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...
340
     *
341
     * @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...
342
     */
343
    public function getTagsList($category, $value = null)
344
    {
345
        $tags = $this->getTagsObjects($category, $value);
346
347
        $returnArray = null;
348
349
        if ($tags->Count() > 0) {
350
            $returnArray = array();
351
352
            foreach ($tags as $t) {
353
                $returnArray[] = $t->Value;
354
            }
355
        }
356
357
        return $returnArray;
358
    }
359
360
    /**
361
     * Removes a tag from the Document. If you only set a category, then all
362
     * values in that category are deleted.
363
     *
364
     * If you specify both a category and a value, then only that single
365
     * category/value pair is deleted.
366
     *
367
     * Nothing happens if the category or the value do not exist.
368
     *
369
     * @param string $category Category to remove
370
     * @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...
371
     *
372
     * @return DMSDocument
373
     */
374
    public function removeTag($category, $value = null)
375
    {
376
        $tags = $this->getTagsObjects($category, $value);
377
378
        if ($tags->Count() > 0) {
379
            foreach ($tags as $t) {
380
                $documentList = $t->Documents();
381
382
                //remove the relation between the tag and the document
383
                $documentList->remove($this);
384
385
                //delete the entire tag if it has no relations left
386
                if ($documentList->Count() == 0) {
387
                    $t->delete();
388
                }
389
            }
390
        }
391
392
        return $this;
393
    }
394
395
    /**
396
     * Deletes all tags associated with this Document.
397
     *
398
     * @return DMSDocument
399
     */
400
    public function removeAllTags()
401
    {
402
        $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...
403
404
        foreach ($allTags as $tag) {
405
            $documentlist = $tag->Documents();
406
            $documentlist->remove($this);
407
            if ($tag->Documents()->Count() == 0) {
408
                $tag->delete();
409
            }
410
        }
411
412
        return $this;
413
    }
414
415
    /**
416
     * Returns a link to download this document from the DMS store.
417
     * Alternatively a basic javascript alert will be shown should the user not have view permissions. An extension
418
     * point for this was also added.
419
     *
420
     * To extend use the following from within an Extension subclass:
421
     *
422
     * <code>
423
     * public function updateGetLink($result)
424
     * {
425
     *     // Do something here
426
     * }
427
     * </code>
428
     *
429
     * @return string
430
     */
431
    public function getLink()
432
    {
433
        $result = Controller::join_links(Director::baseURL(), 'dmsdocument/' . $this->ID);
434
        if (!$this->canView()) {
435
            $result = sprintf("javascript:alert('%s')", $this->getPermissionDeniedReason());
436
        }
437
438
        $this->extend('updateGetLink', $result);
439
440
        return $result;
441
    }
442
443
    /**
444
     * @return string
445
     */
446
    public function Link()
447
    {
448
        return $this->getLink();
449
    }
450
451
    /**
452
     * Hides the document, so it does not show up when getByPage($myPage) is
453
     * called (without specifying the $showEmbargoed = true parameter).
454
     *
455
     * This is similar to expire, except that this method should be used to hide
456
     * documents that have not yet gone live.
457
     *
458
     * @param bool $write Save change to the database
459
     *
460
     * @return DMSDocument
461
     */
462
    public function embargoIndefinitely($write = true)
463
    {
464
        $this->EmbargoedIndefinitely = true;
465
466
        if ($write) {
467
            $this->write();
468
        }
469
470
        return $this;
471
    }
472
473
    /**
474
     * Hides the document until any page it is linked to is published
475
     *
476
     * @param bool $write Save change to database
477
     *
478
     * @return DMSDocument
479
     */
480
    public function embargoUntilPublished($write = true)
481
    {
482
        $this->EmbargoedUntilPublished = true;
483
484
        if ($write) {
485
            $this->write();
486
        }
487
488
        return $this;
489
    }
490
491
    /**
492
     * Returns if this is Document is embargoed or expired.
493
     *
494
     * Also, returns if the document should be displayed on the front-end,
495
     * respecting the current reading mode of the site and the embargo status.
496
     *
497
     * I.e. if a document is embargoed until published, then it should still
498
     * show up in draft mode.
499
     *
500
     * @return bool
501
     */
502
    public function isHidden()
503
    {
504
        $hidden = $this->isEmbargoed() || $this->isExpired();
505
        $readingMode = Versioned::get_reading_mode();
506
507
        if ($readingMode == "Stage.Stage") {
508
            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...
509
                $hidden = false;
510
            }
511
        }
512
513
        return $hidden;
514
    }
515
516
    /**
517
     * Returns if this is Document is embargoed.
518
     *
519
     * @return bool
520
     */
521
    public function isEmbargoed()
522
    {
523
        if (is_object($this->EmbargoedUntilDate)) {
524
            $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...
525
        }
526
527
        $embargoed = false;
528
529
        if ($this->EmbargoedIndefinitely) {
530
            $embargoed = true;
531
        } elseif ($this->EmbargoedUntilPublished) {
532
            $embargoed = true;
533
        } elseif (!empty($this->EmbargoedUntilDate)) {
534
            if (SS_Datetime::now()->Value < $this->EmbargoedUntilDate) {
535
                $embargoed = true;
536
            }
537
        }
538
539
        return $embargoed;
540
    }
541
542
    /**
543
     * Hides the document, so it does not show up when getByPage($myPage) is
544
     * called. Automatically un-hides the Document at a specific date.
545
     *
546
     * @param string $datetime date time value when this Document should expire.
547
     * @param bool $write
548
     *
549
     * @return DMSDocument
550
     */
551 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...
552
    {
553
        $this->EmbargoedUntilDate = DBField::create_field('SS_Datetime', $datetime)->Format('Y-m-d H:i:s');
554
555
        if ($write) {
556
            $this->write();
557
        }
558
559
        return $this;
560
    }
561
562
    /**
563
     * Clears any previously set embargos, so the Document always shows up in
564
     * all queries.
565
     *
566
     * @param bool $write
567
     *
568
     * @return DMSDocument
569
     */
570
    public function clearEmbargo($write = true)
571
    {
572
        $this->EmbargoedIndefinitely = false;
573
        $this->EmbargoedUntilPublished = false;
574
        $this->EmbargoedUntilDate = null;
575
576
        if ($write) {
577
            $this->write();
578
        }
579
580
        return $this;
581
    }
582
583
    /**
584
     * Returns if this is Document is expired.
585
     *
586
     * @return bool
587
     */
588
    public function isExpired()
589
    {
590
        if (is_object($this->ExpireAtDate)) {
591
            $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...
592
        }
593
594
        $expired = false;
595
596
        if (!empty($this->ExpireAtDate)) {
597
            if (SS_Datetime::now()->Value >= $this->ExpireAtDate) {
598
                $expired = true;
599
            }
600
        }
601
602
        return $expired;
603
    }
604
605
    /**
606
     * Hides the document at a specific date, so it does not show up when
607
     * getByPage($myPage) is called.
608
     *
609
     * @param string $datetime date time value when this Document should expire
610
     * @param bool $write
611
     *
612
     * @return DMSDocument
613
     */
614 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...
615
    {
616
        $this->ExpireAtDate = DBField::create_field('SS_Datetime', $datetime)->Format('Y-m-d H:i:s');
617
618
        if ($write) {
619
            $this->write();
620
        }
621
622
        return $this;
623
    }
624
625
    /**
626
     * Clears any previously set expiry.
627
     *
628
     * @param bool $write
629
     *
630
     * @return DMSDocument
631
     */
632
    public function clearExpiry($write = true)
633
    {
634
        $this->ExpireAtDate = null;
635
636
        if ($write) {
637
            $this->write();
638
        }
639
640
        return $this;
641
    }
642
643
    /**
644
     * Returns a DataList of all previous Versions of this document (check the
645
     * LastEdited date of each object to find the correct one).
646
     *
647
     * If {@link DMSDocument_versions::$enable_versions} is disabled then an
648
     * Exception is thrown
649
     *
650
     * @throws Exception
651
     *
652
     * @return DataList List of Document objects
653
     */
654
    public function getVersions()
655
    {
656
        if (!DMSDocument_versions::$enable_versions) {
657
            throw new Exception("DMSDocument versions are disabled");
658
        }
659
660
        return DMSDocument_versions::get_versions($this);
661
    }
662
663
    /**
664
     * Returns the full filename of the document stored in this object.
665
     *
666
     * @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...
667
     */
668
    public function getFullPath()
669
    {
670
        if ($this->Filename) {
671
            return DMS::get_dms_path() . DIRECTORY_SEPARATOR . $this->Folder . DIRECTORY_SEPARATOR . $this->Filename;
672
        }
673
674
        return null;
675
    }
676
677
    /**
678
     * Returns the filename of this asset.
679
     *
680
     * @return string
681
     */
682
    public function getFileName()
683
    {
684
        if ($this->getField('Filename')) {
685
            return $this->getField('Filename');
686
        } else {
687
            return ASSETS_DIR . '/';
688
        }
689
    }
690
691
    /**
692
     * @return string
693
     */
694
    public function getName()
695
    {
696
        return $this->getField('Title');
697
    }
698
699
700
    /**
701
     * @return string
702
     */
703
    public function getFilenameWithoutID()
704
    {
705
        $filenameParts = explode('~', $this->Filename);
706
        $filename = array_pop($filenameParts);
707
708
        return $filename;
709
    }
710
711
    /**
712
     * @return string
713
     */
714
    public function getStorageFolder()
715
    {
716
        return DMS::get_dms_path() . DIRECTORY_SEPARATOR . DMS::get_storage_folder($this->ID);
717
    }
718
719
    /**
720
     * Deletes the DMSDocument, its underlying file, as well as any tags related
721
     * to this DMSDocument. Also calls the parent DataObject's delete method in
722
     * order to complete an cascade.
723
     *
724
     * @return void
725
     */
726
    public function delete()
727
    {
728
        // remove tags
729
        $this->removeAllTags();
730
731
        // delete the file (and previous versions of files)
732
        $filesToDelete = array();
733
        $storageFolder = $this->getStorageFolder();
734
735
        if (file_exists($storageFolder)) {
736
            if ($handle = opendir($storageFolder)) {
737
                while (false !== ($entry = readdir($handle))) {
738
                    // only delete if filename starts the the relevant ID
739
                    if (strpos($entry, $this->ID.'~') === 0) {
740
                        $filesToDelete[] = $entry;
741
                    }
742
                }
743
744
                closedir($handle);
745
746
                //delete all this files that have the id of this document
747
                foreach ($filesToDelete as $file) {
748
                    $filePath = $storageFolder .DIRECTORY_SEPARATOR . $file;
749
750
                    if (is_file($filePath)) {
751
                        unlink($filePath);
752
                    }
753
                }
754
            }
755
        }
756
757
        // get rid of any versions have saved for this DMSDocument, too
758
        if (DMSDocument_versions::$enable_versions) {
759
            $versions = $this->getVersions();
760
761
            if ($versions->Count() > 0) {
762
                foreach ($versions as $v) {
763
                    $v->delete();
764
                }
765
            }
766
        }
767
768
        return parent::delete();
769
    }
770
771
    /**
772
     * Relate an existing file on the filesystem to the document.
773
     *
774
     * Copies the file to the new destination, as defined in {@link get_DMS_path()}.
775
     *
776
     * @param string $filePath Path to file, relative to webroot.
777
     *
778
     * @return DMSDocument
779
     */
780
    public function storeDocument($filePath)
781
    {
782
        if (empty($this->ID)) {
783
            user_error("Document must be written to database before it can store documents", E_USER_ERROR);
784
        }
785
786
        // calculate all the path to copy the file to
787
        $fromFilename = basename($filePath);
788
        $toFilename = $this->ID. '~' . $fromFilename; //add the docID to the start of the Filename
789
        $toFolder = DMS::get_storage_folder($this->ID);
790
        $toPath = DMS::get_dms_path() . DIRECTORY_SEPARATOR . $toFolder . DIRECTORY_SEPARATOR . $toFilename;
791
792
        DMS::create_storage_folder(DMS::get_dms_path() . DIRECTORY_SEPARATOR . $toFolder);
793
794
        //copy the file into place
795
        $fromPath = BASE_PATH . DIRECTORY_SEPARATOR . $filePath;
796
797
        //version the existing file (copy it to a new "very specific" filename
798
        if (DMSDocument_versions::$enable_versions) {
799
            DMSDocument_versions::create_version($this);
800
        } else {    //otherwise delete the old document file
801
            $oldPath = $this->getFullPath();
802
            if (file_exists($oldPath)) {
803
                unlink($oldPath);
804
            }
805
        }
806
807
        copy($fromPath, $toPath);   //this will overwrite the existing file (if present)
808
809
        //write the filename of the stored document
810
        $this->Filename = $toFilename;
811
        $this->Folder = strval($toFolder);
812
813
        $extension = pathinfo($this->Filename, PATHINFO_EXTENSION);
814
815
        if (empty($this->Title)) {
816
            // don't overwrite existing document titles
817
            $this->Title = basename($filePath, '.'.$extension);
818
        }
819
820
        $this->LastChanged = SS_Datetime::now()->Rfc2822();
821
        $this->write();
822
823
        return $this;
824
    }
825
826
    /**
827
     * Takes a File object or a String (path to a file) and copies it into the
828
     * DMS, replacing the original document file but keeping the rest of the
829
     * document unchanged.
830
     *
831
     * @param File|string $file path to a file to store
832
     *
833
     * @return DMSDocument object that we replaced the file in
834
     */
835
    public function replaceDocument($file)
836
    {
837
        $filePath = DMS::transform_file_to_file_path($file);
838
        $doc = $this->storeDocument($filePath); // replace the document
839
840
        return $doc;
841
    }
842
843
844
    /**
845
     * Return the type of file for the given extension
846
     * on the current file name.
847
     *
848
     * @param string $ext
849
     *
850
     * @return string
851
     */
852
    public static function get_file_type($ext)
853
    {
854
        $types = array(
855
            'gif' => 'GIF image - good for diagrams',
856
            'jpg' => 'JPEG image - good for photos',
857
            'jpeg' => 'JPEG image - good for photos',
858
            'png' => 'PNG image - good general-purpose format',
859
            'ico' => 'Icon image',
860
            'tiff' => 'Tagged image format',
861
            'doc' => 'Word document',
862
            'xls' => 'Excel spreadsheet',
863
            'zip' => 'ZIP compressed file',
864
            'gz' => 'GZIP compressed file',
865
            'dmg' => 'Apple disk image',
866
            'pdf' => 'Adobe Acrobat PDF file',
867
            'mp3' => 'MP3 audio file',
868
            'wav' => 'WAV audo file',
869
            'avi' => 'AVI video file',
870
            'mpg' => 'MPEG video file',
871
            'mpeg' => 'MPEG video file',
872
            'js' => 'Javascript file',
873
            'css' => 'CSS file',
874
            'html' => 'HTML file',
875
            'htm' => 'HTML file'
876
        );
877
878
        return isset($types[$ext]) ? $types[$ext] : $ext;
879
    }
880
881
882
    /**
883
     * Returns the Description field with HTML <br> tags added when there is a
884
     * line break.
885
     *
886
     * @return string
887
     */
888
    public function getDescriptionWithLineBreak()
889
    {
890
        return nl2br($this->getField('Description'));
891
    }
892
893
    /**
894
     * @return FieldList
895
     */
896
    public function getCMSFields()
897
    {
898
        //include JS to handling showing and hiding of bottom "action" tabs
899
        Requirements::javascript(DMS_DIR . '/javascript/DMSDocumentCMSFields.js');
900
        Requirements::css(DMS_DIR . '/dist/css/cmsbundle.css');
901
902
        $fields = new FieldList();  //don't use the automatic scaffolding, it is slow and unnecessary here
903
904
        $extraTasks = '';   //additional text to inject into the list of tasks at the bottom of a DMSDocument CMSfield
0 ignored issues
show
Unused Code introduced by
$extraTasks is not used, you could remove the assignment.

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

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

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

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

Loading history...
905
906
        //get list of shortcode page relations
907
        $relationFinder = new ShortCodeRelationFinder();
908
        $relationList = $relationFinder->getList($this->ID);
909
910
        $fieldsTop = $this->getFieldsForFile($relationList->count());
911
        $fields->add($fieldsTop);
912
913
        $fields->add(TextField::create('Title', _t('DMSDocument.TITLE', 'Title')));
914
        $fields->add(TextareaField::create('Description', _t('DMSDocument.DESCRIPTION', 'Description')));
915
916
        $coverImageField = UploadField::create('CoverImage', _t('DMSDocument.COVERIMAGE', 'Cover Image'));
917
        $coverImageField->getValidator()->setAllowedExtensions(array('jpg', 'jpeg', 'png', 'gif'));
918
        $coverImageField->setConfig('allowedMaxFileNumber', 1);
919
        $fields->add($coverImageField);
920
921
922
        $downloadBehaviorSource = array(
923
            'open' => _t('DMSDocument.OPENINBROWSER', 'Open in browser'),
924
            'download' => _t('DMSDocument.FORCEDOWNLOAD', 'Force download'),
925
        );
926
        $defaultDownloadBehaviour = Config::inst()->get('DMSDocument', 'default_download_behaviour');
927
        if (!isset($downloadBehaviorSource[$defaultDownloadBehaviour])) {
928
            user_error('Default download behaviour "' . $defaultDownloadBehaviour . '" not supported.', E_USER_WARNING);
929
        } else {
930
            $downloadBehaviorSource[$defaultDownloadBehaviour] .= ' (' . _t('DMSDocument.DEFAULT', 'default') . ')';
931
        }
932
933
        $fields->add(
934
            OptionsetField::create(
935
                'DownloadBehavior',
936
                _t('DMSDocument.DOWNLOADBEHAVIOUR', 'Download behavior'),
937
                $downloadBehaviorSource,
938
                $defaultDownloadBehaviour
939
            )
940
            ->setDescription(
941
                'How the visitor will view this file. <strong>Open in browser</strong> '
942
                . 'allows files to be opened in a new tab.'
943
            )
944
        );
945
946
        //create upload field to replace document
947
        $uploadField = new DMSUploadField('ReplaceFile', 'Replace file');
948
        $uploadField->setConfig('allowedMaxFileNumber', 1);
949
        $uploadField->setConfig('downloadTemplateName', 'ss-dmsuploadfield-downloadtemplate');
950
        $uploadField->setRecord($this);
951
952
        $gridFieldConfig = GridFieldConfig::create()->addComponents(
953
            new GridFieldToolbarHeader(),
954
            new GridFieldSortableHeader(),
955
            new GridFieldDataColumns(),
956
            new GridFieldPaginator(30),
957
            //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...
958
            new GridFieldDetailForm()
959
        );
960
961
        $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...
962
            ->setDisplayFields(array(
963
                'Title'=>'Title',
964
                'ClassName'=>'Page Type',
965
                'ID'=>'Page ID'
966
            ))
967
            ->setFieldFormatting(array(
968
                'Title'=>sprintf(
969
                    '<a class=\"cms-panel-link\" href=\"%s/$ID\">$Title</a>',
970
                    singleton('CMSPageEditController')->Link('show')
971
                )
972
            ));
973
974
        $pagesGrid = GridField::create(
975
            'Pages',
976
            _t('DMSDocument.RelatedPages', 'Related Pages'),
977
            $this->getRelatedPages(),
978
            $gridFieldConfig
979
        );
980
981
        $referencesGrid = GridField::create(
982
            'References',
983
            _t('DMSDocument.RelatedReferences', 'Related References'),
984
            $relationList,
985
            $gridFieldConfig
986
        );
987
988
        if (DMSDocument_versions::$enable_versions) {
989
            $versionsGridFieldConfig = GridFieldConfig::create()->addComponents(
990
                new GridFieldToolbarHeader(),
991
                new GridFieldSortableHeader(),
992
                new GridFieldDataColumns(),
993
                new GridFieldPaginator(30)
994
            );
995
            $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...
996
                ->setDisplayFields(Config::inst()->get('DMSDocument_versions', 'display_fields'))
997
                ->setFieldCasting(array('LastChanged'=>"Datetime->Ago"))
998
                ->setFieldFormatting(
999
                    array(
1000
                        'FilenameWithoutID' => '<a target=\'_blank\' class=\'file-url\' href=\'$Link\'>'
1001
                            . '$FilenameWithoutID</a>'
1002
                    )
1003
                );
1004
1005
            $versionsGrid =  GridField::create(
1006
                'Versions',
1007
                _t('DMSDocument.Versions', 'Versions'),
1008
                $this->getVersions(),
1009
                $versionsGridFieldConfig
1010
            );
1011
            $this->addActionPanelTask('find-versions', 'Versions');
1012
        }
1013
1014
        $fields->add(LiteralField::create('BottomTaskSelection', $this->getActionTaskHtml()));
1015
1016
        $embargoValue = 'None';
1017
        if ($this->EmbargoedIndefinitely) {
1018
            $embargoValue = 'Indefinitely';
1019
        } elseif ($this->EmbargoedUntilPublished) {
1020
            $embargoValue = 'Published';
1021
        } elseif (!empty($this->EmbargoedUntilDate)) {
1022
            $embargoValue = 'Date';
1023
        }
1024
        $embargo = new OptionsetField(
1025
            'Embargo',
1026
            _t('DMSDocument.EMBARGO', 'Embargo'),
1027
            array(
1028
                'None' => _t('DMSDocument.EMBARGO_NONE', 'None'),
1029
                'Published' => _t('DMSDocument.EMBARGO_PUBLISHED', 'Hide document until page is published'),
1030
                'Indefinitely' => _t('DMSDocument.EMBARGO_INDEFINITELY', 'Hide document indefinitely'),
1031
                'Date' => _t('DMSDocument.EMBARGO_DATE', 'Hide until set date')
1032
            ),
1033
            $embargoValue
1034
        );
1035
        $embargoDatetime = DatetimeField::create('EmbargoedUntilDate', '');
1036
        $embargoDatetime->getDateField()
1037
            ->setConfig('showcalendar', true)
1038
            ->setConfig('dateformat', 'dd-MM-yyyy')
1039
            ->setConfig('datavalueformat', 'dd-MM-yyyy');
1040
1041
        $expiryValue = 'None';
1042
        if (!empty($this->ExpireAtDate)) {
1043
            $expiryValue = 'Date';
1044
        }
1045
        $expiry = new OptionsetField(
1046
            'Expiry',
1047
            'Expiry',
1048
            array(
1049
                'None' => 'None',
1050
                'Date' => 'Set document to expire on'
1051
            ),
1052
            $expiryValue
1053
        );
1054
        $expiryDatetime = DatetimeField::create('ExpireAtDate', '');
1055
        $expiryDatetime->getDateField()
1056
            ->setConfig('showcalendar', true)
1057
            ->setConfig('dateformat', 'dd-MM-yyyy')
1058
            ->setConfig('datavalueformat', 'dd-MM-yyyy');
1059
1060
        // This adds all the actions details into a group.
1061
        // Embargo, History, etc to go in here
1062
        // These are toggled on and off via the Actions Buttons above
1063
        // 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...
1064
        $actionsPanel = FieldGroup::create(
1065
            FieldGroup::create($embargo, $embargoDatetime)->addExtraClass('embargo'),
1066
            FieldGroup::create($expiry, $expiryDatetime)->addExtraClass('expiry'),
1067
            FieldGroup::create($uploadField)->addExtraClass('replace'),
1068
            FieldGroup::create($pagesGrid)->addExtraClass('find-usage'),
1069
            FieldGroup::create($referencesGrid)->addExtraClass('find-references'),
1070
            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...
1071
            FieldGroup::create($this->getRelatedDocumentsGridField())->addExtraClass('find-relateddocuments'),
1072
            FieldGroup::create($this->getPermissionsActionPanel())->addExtraClass('permissions')
1073
        );
1074
1075
        $actionsPanel->setName("ActionsPanel");
1076
        $actionsPanel->addExtraClass("DMSDocumentActionsPanel");
1077
        $fields->push($actionsPanel);
1078
1079
        $this->extend('updateCMSFields', $fields);
1080
1081
        return $fields;
1082
    }
1083
1084
    /**
1085
     * Adds permissions selection fields to a composite field and returns so it can be used in the "actions panel"
1086
     *
1087
     * @return CompositeField
1088
     */
1089
    public function getPermissionsActionPanel()
1090
    {
1091
        $fields = FieldList::create();
1092
        $showFields = array(
1093
            'CanViewType'  => '',
1094
            'ViewerGroups' => 'hide',
1095
            'CanEditType'  => '',
1096
            'EditorGroups' => 'hide',
1097
        );
1098
        /** @var SiteTree $siteTree */
1099
        $siteTree = singleton('SiteTree');
1100
        $settingsFields = $siteTree->getSettingsFields();
1101
1102
        foreach ($showFields as $name => $extraCss) {
1103
            $compositeName = "Root.Settings.$name";
1104
            /** @var FormField $field */
1105
            if ($field = $settingsFields->fieldByName($compositeName)) {
1106
                $field->addExtraClass($extraCss);
1107
                $title = str_replace('page', 'document', $field->Title());
1108
                $field->setTitle($title);
1109
1110
                // Remove Inherited source option from DropdownField
1111
                if ($field instanceof DropdownField) {
1112
                    $options = $field->getSource();
1113
                    unset($options['Inherit']);
1114
                    $field->setSource($options);
1115
                }
1116
                $fields->push($field);
1117
            }
1118
        }
1119
1120
        $this->extend('updatePermissionsFields', $fields);
1121
1122
        return CompositeField::create($fields);
1123
    }
1124
1125
    /**
1126
     * Return a title to use on the frontend, preferably the "title", otherwise the filename without it's numeric ID
1127
     *
1128
     * @return string
1129
     */
1130
    public function getTitle()
1131
    {
1132
        if ($this->getField('Title')) {
1133
            return $this->getField('Title');
1134
        }
1135
        return $this->FilenameWithoutID;
0 ignored issues
show
Bug introduced by
The property FilenameWithoutID does not seem to exist. Did you mean Filename?

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...
1136
    }
1137
1138
    public function onBeforeWrite()
1139
    {
1140
        parent::onBeforeWrite();
1141
1142
        if (isset($this->Embargo)) {
1143
            //set the embargo options from the OptionSetField created in the getCMSFields method
1144
            //do not write after clearing the embargo (write happens automatically)
1145
            $savedDate = $this->EmbargoedUntilDate;
1146
            $this->clearEmbargo(false); //clear all previous settings and re-apply them on save
1147
1148
            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...
1149
                $this->embargoUntilPublished(false);
1150
            }
1151
            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...
1152
                $this->embargoIndefinitely(false);
1153
            }
1154
            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...
1155
                $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...
1156
            }
1157
        }
1158
1159
        if (isset($this->Expiry)) {
1160
            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...
1161
                $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...
1162
            } else {
1163
                $this->clearExpiry(false);
1164
            } //clear all previous settings
1165
        }
1166
    }
1167
1168
    /**
1169
     * Return the relative URL of an icon for the file type, based on the
1170
     * {@link appCategory()} value.
1171
     *
1172
     * Images are searched for in "dms/images/app_icons/".
1173
     *
1174
     * @return string
1175
     */
1176
    public function Icon($ext)
1177
    {
1178
        if (!Director::fileExists(DMS_DIR."/images/app_icons/{$ext}_32.png")) {
1179
            $ext = File::get_app_category($ext);
1180
        }
1181
1182
        if (!Director::fileExists(DMS_DIR."/images/app_icons/{$ext}_32.png")) {
1183
            $ext = "generic";
1184
        }
1185
1186
        return DMS_DIR."/images/app_icons/{$ext}_32.png";
1187
    }
1188
1189
    /**
1190
     * Return the extension of the file associated with the document
1191
     *
1192
     * @return string
1193
     */
1194
    public function getExtension()
1195
    {
1196
        return strtolower(pathinfo($this->Filename, PATHINFO_EXTENSION));
1197
    }
1198
1199
    /**
1200
     * @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...
1201
     */
1202
    public function getSize()
1203
    {
1204
        $size = $this->getAbsoluteSize();
1205
        return ($size) ? File::format_size($size) : false;
1206
    }
1207
1208
    /**
1209
     * Return the size of the file associated with the document.
1210
     *
1211
     * @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...
1212
     */
1213
    public function getAbsoluteSize()
1214
    {
1215
        return file_exists($this->getFullPath()) ? filesize($this->getFullPath()) : null;
1216
    }
1217
1218
    /**
1219
     * An alias to DMSDocument::getSize()
1220
     *
1221
     * @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...
1222
     */
1223
    public function getFileSizeFormatted()
1224
    {
1225
        return $this->getSize();
1226
    }
1227
1228
1229
    /**
1230
     * @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...
1231
     */
1232
    protected function getFieldsForFile($relationListCount)
1233
    {
1234
        $extension = $this->getExtension();
1235
1236
        $previewField = new LiteralField(
1237
            "ImageFull",
1238
            "<img id='thumbnailImage' class='thumbnail-preview' src='{$this->Icon($extension)}?r="
1239
            . rand(1, 100000) . "' alt='{$this->Title}' />\n"
1240
        );
1241
1242
        //count the number of pages this document is published on
1243
        $publishedOnCount = $this->getRelatedPages()->count();
1244
        $publishedOnValue = "$publishedOnCount pages";
1245
        if ($publishedOnCount == 1) {
1246
            $publishedOnValue = "$publishedOnCount page";
1247
        }
1248
1249
        $relationListCountValue = "$relationListCount pages";
1250
        if ($relationListCount == 1) {
1251
            $relationListCountValue = "$relationListCount page";
1252
        }
1253
1254
        $fields = new FieldGroup(
1255
            $filePreview = CompositeField::create(
1256
                CompositeField::create(
1257
                    $previewField
1258
                )->setName("FilePreviewImage")->addExtraClass('cms-file-info-preview'),
1259
                CompositeField::create(
1260
                    CompositeField::create(
1261
                        new ReadonlyField("ID", "ID number". ':', $this->ID),
1262
                        new ReadonlyField(
1263
                            "FileType",
1264
                            _t('AssetTableField.TYPE', 'File type') . ':',
1265
                            self::get_file_type($extension)
1266
                        ),
1267
                        new ReadonlyField(
1268
                            "Size",
1269
                            _t('AssetTableField.SIZE', 'File size') . ':',
1270
                            $this->getFileSizeFormatted()
1271
                        ),
1272
                        $urlField = new ReadonlyField(
1273
                            'ClickableURL',
1274
                            _t('AssetTableField.URL', 'URL'),
1275
                            sprintf(
1276
                                '<a href="%s" target="_blank" class="file-url">%s</a>',
1277
                                $this->getLink(),
1278
                                $this->getLink()
1279
                            )
1280
                        ),
1281
                        new ReadonlyField("FilenameWithoutIDField", "Filename". ':', $this->getFilenameWithoutID()),
1282
                        new DateField_Disabled(
1283
                            "Created",
1284
                            _t('AssetTableField.CREATED', 'First uploaded') . ':',
1285
                            $this->Created
1286
                        ),
1287
                        new DateField_Disabled(
1288
                            "LastEdited",
1289
                            _t('AssetTableField.LASTEDIT', 'Last changed') . ':',
1290
                            $this->LastEdited
1291
                        ),
1292
                        new DateField_Disabled(
1293
                            "LastChanged",
1294
                            _t('AssetTableField.LASTCHANGED', 'Last replaced') . ':',
1295
                            $this->LastChanged
1296
                        ),
1297
                        new ReadonlyField("PublishedOn", "Published on". ':', $publishedOnValue),
1298
                        new ReadonlyField("ReferencedOn", "Referenced on". ':', $relationListCountValue),
1299
                        new ReadonlyField("ViewCount", "View count". ':', $this->ViewCount)
1300
                    )
1301
                )->setName("FilePreviewData")->addExtraClass('cms-file-info-data')
1302
            )->setName("FilePreview")->addExtraClass('cms-file-info')
1303
        );
1304
1305
        $fields->setName('FileP');
1306
        $urlField->dontEscape = true;
1307
1308
        return $fields;
1309
    }
1310
1311
    /**
1312
     * Takes a file and adds it to the DMSDocument storage, replacing the
1313
     * current file.
1314
     *
1315
     * @param File $file
1316
     *
1317
     * @return $this
1318
     */
1319
    public function ingestFile($file)
1320
    {
1321
        $this->replaceDocument($file);
1322
        $file->delete();
1323
1324
        return $this;
1325
    }
1326
1327
    /**
1328
     * Get a data list of documents related to this document
1329
     *
1330
     * @return DataList
1331
     */
1332
    public function getRelatedDocuments()
1333
    {
1334
        $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...
1335
1336
        $this->extend('updateRelatedDocuments', $documents);
1337
1338
        return $documents;
1339
    }
1340
1341
    /**
1342
     * Get a list of related pages for this document by going through the associated document sets
1343
     *
1344
     * @return ArrayList
1345
     */
1346
    public function getRelatedPages()
1347
    {
1348
        $pages = ArrayList::create();
1349
1350
        foreach ($this->Sets() as $documentSet) {
0 ignored issues
show
Documentation Bug introduced by
The method Sets 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...
1351
            /** @var DocumentSet $documentSet */
1352
            $pages->add($documentSet->Page());
1353
        }
1354
        $pages->removeDuplicates();
1355
1356
        $this->extend('updateRelatedPages', $pages);
1357
1358
        return $pages;
1359
    }
1360
1361
    /**
1362
     * Get a GridField for managing related documents
1363
     *
1364
     * @return GridField
1365
     */
1366
    protected function getRelatedDocumentsGridField()
1367
    {
1368
        $gridField = GridField::create(
1369
            'RelatedDocuments',
1370
            _t('DMSDocument.RELATEDDOCUMENTS', 'Related Documents'),
1371
            $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...
1372
            new GridFieldConfig_RelationEditor
1373
        );
1374
1375
        $gridField->getConfig()->removeComponentsByType('GridFieldAddNewButton');
1376
        // Move the autocompleter to the left
1377
        $gridField->getConfig()->removeComponentsByType('GridFieldAddExistingAutocompleter');
1378
        $gridField->getConfig()->addComponent(
1379
            $addExisting = new GridFieldAddExistingAutocompleter('buttons-before-left')
1380
        );
1381
1382
        // Ensure that current document doesn't get returned in the autocompleter
1383
        $addExisting->setSearchList($this->getRelatedDocumentsForAutocompleter());
1384
1385
        $this->extend('updateRelatedDocumentsGridField', $gridField);
1386
1387
        return $gridField;
1388
    }
1389
1390
    /**
1391
     * Get the list of documents to show in "related documents". This can be modified via the extension point, for
1392
     * example if you wanted to exclude embargoed documents or something similar.
1393
     *
1394
     * @return DataList
1395
     */
1396
    protected function getRelatedDocumentsForAutocompleter()
1397
    {
1398
        $documents = DMSDocument::get()->exclude('ID', $this->ID);
1399
        $this->extend('updateRelatedDocumentsForAutocompleter', $documents);
1400
        return $documents;
1401
    }
1402
1403
    /**
1404
     * Checks at least one group is selected if CanViewType || CanEditType == 'OnlyTheseUsers'
1405
     *
1406
     * @return ValidationResult
1407
     */
1408
    protected function validate()
1409
    {
1410
        $valid = parent::validate();
1411
1412
        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...
1413
            $valid->error(
1414
                _t(
1415
                    'DMSDocument.VALIDATIONERROR_NOVIEWERSELECTED',
1416
                    "Selecting 'Only these people' from a viewers list needs at least one group selected."
1417
                )
1418
            );
1419
        }
1420
1421
        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...
1422
            $valid->error(
1423
                _t(
1424
                    'DMSDocument.VALIDATIONERROR_NOEDITORSELECTED',
1425
                    "Selecting 'Only these people' from a editors list needs at least one group selected."
1426
                )
1427
            );
1428
        }
1429
1430
        return $valid;
1431
    }
1432
1433
    /**
1434
     * Returns a reason as to why this document cannot be viewed.
1435
     *
1436
     * @return string
1437
     */
1438
    public function getPermissionDeniedReason()
1439
    {
1440
        $result = '';
1441
1442
        if ($this->CanViewType == 'LoggedInUsers') {
1443
            $result = _t('DMSDocument.PERMISSIONDENIEDREASON_LOGINREQUIRED', 'Please log in to view this document');
1444
        }
1445
1446
        if ($this->CanViewType == 'OnlyTheseUsers') {
1447
            $result = _t(
1448
                'DMSDocument.PERMISSIONDENIEDREASON_NOTAUTHORISED',
1449
                'You are not authorised to view this document'
1450
            );
1451
        }
1452
1453
        return $result;
1454
    }
1455
1456
    /**
1457
     * Add an "action panel" task
1458
     *
1459
     * @param  string $panelKey
1460
     * @param  string $title
1461
     * @return $this
1462
     */
1463
    public function addActionPanelTask($panelKey, $title)
1464
    {
1465
        $this->actionTasks[$panelKey] = $title;
1466
        return $this;
1467
    }
1468
1469
    /**
1470
     * Returns a HTML representation of the action tasks for the CMS
1471
     *
1472
     * @return string
1473
     */
1474
    public function getActionTaskHtml()
1475
    {
1476
        $html = '<div id="Actions" class="field actions">'
1477
            . '<label class="left">' . _t('DMSDocument.ACTIONS_LABEL', 'Actions') . '</label>'
1478
            . '<ul>';
1479
1480
        foreach ($this->actionTasks as $panelKey => $title) {
1481
            $html .= '<li class="ss-ui-button" data-panel="' . $panelKey . '">'
1482
                . _t('DMSDocument.ACTION_' . strtoupper($panelKey), $title)
1483
                . '</li>';
1484
        }
1485
1486
        $html .= '</ul></div>';
1487
1488
        return $html;
1489
    }
1490
}
1491