Issues (399)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

code/model/DMSDocumentSet.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * A document set is attached to Pages, and contains many DMSDocuments
4
 *
5
 * @property Varchar Title
6
 * @property  Text KeyValuePairs
7
 * @property  Enum SortBy
8
 * @property Enum SortByDirection
9
 */
10
class DMSDocumentSet extends DataObject
11
{
12
    private static $db = array(
13
        'Title' => 'Varchar(255)',
14
        'KeyValuePairs' => 'Text',
15
        'SortBy' => "Enum('LastEdited,Created,Title')')",
16
        'SortByDirection' => "Enum('DESC,ASC')')",
17
    );
18
19
    private static $has_one = array(
20
        'Page' => 'SiteTree',
21
    );
22
23
    private static $many_many = array(
24
        'Documents' => 'DMSDocument',
25
    );
26
27
    private static $many_many_extraFields = array(
28
        'Documents' => array(
29
            // Flag indicating if a document was added directly to a set - in which case it is set - or added
30
            // via the query-builder.
31
            'ManuallyAdded' => 'Boolean(1)',
32
            'DocumentSort' => 'Int'
33
        ),
34
    );
35
36
    private static $summary_fields = array(
37
        'Title' => 'Title',
38
        'Documents.Count' => 'No. Documents'
39
    );
40
41
    /**
42
     * Retrieve a list of the documents in this set. An extension hook is provided before the result is returned.
43
     *
44
     * You can attach an extension to this event:
45
     *
46
     * <code>
47
     * public function updateDocuments($document)
48
     * {
49
     *     // do something
50
     * }
51
     * </code>
52
     *
53
     * @return DataList|null
54
     */
55
    public function getDocuments()
56
    {
57
        $documents = $this->Documents();
58
        $this->extend('updateDocuments', $documents);
59
        return $documents;
60
    }
61
62
    /**
63
     * Put the "documents" list into the main tab instead of its own tab, and replace the default "Add Document" button
64
     * with a customised button for DMS documents
65
     *
66
     * @return FieldList
67
     */
68
    public function getCMSFields()
69
    {
70
        // PHP 5.3 only
71
        $self = $this;
72
        $this->beforeUpdateCMSFields(function (FieldList $fields) use ($self) {
73
            $fields->removeFieldsFromTab(
74
                'Root.Main',
75
                array('KeyValuePairs', 'SortBy', 'SortByDirection')
76
            );
77
            // Don't put the GridField for documents in until the set has been created
78
            if (!$self->isInDB()) {
79
                $fields->addFieldToTab(
80
                    'Root.Main',
81
                    LiteralField::create(
82
                        'GridFieldNotice',
83
                        '<p class="message warning">' . _t(
84
                            'DMSDocumentSet.GRIDFIELD_NOTICE',
85
                            'Managing documents will be available once you have created this document set.'
86
                        ) . '</p>'
87
                    ),
88
                    'Title'
89
                );
90
            } else {
91
                $fields->removeByName('DocumentSetSort');
92
                // Document listing
93
                $gridFieldConfig = GridFieldConfig::create()
94
                    ->addComponents(
95
                        new GridFieldButtonRow('before'),
96
                        new GridFieldToolbarHeader(),
97
                        new GridFieldFilterHeader(),
98
                        new GridFieldSortableHeader(),
99
                        new GridFieldDataColumns(),
100
                        new DMSGridFieldEditButton(),
101
                        // Special delete dialog to handle custom behaviour of unlinking and deleting
102
                        new GridFieldDeleteAction(true),
103
                        new GridFieldDetailForm()
104
                    );
105
106
                if (class_exists('GridFieldPaginatorWithShowAll')) {
107
                    $paginatorComponent = new GridFieldPaginatorWithShowAll(15);
108
                } else {
109
                    $paginatorComponent = new GridFieldPaginator(15);
110
                }
111
                $gridFieldConfig->addComponent($paginatorComponent);
112
113
                if (class_exists('GridFieldSortableRows')) {
114
                    $gridFieldConfig->addComponent(new GridFieldSortableRows('DocumentSort'));
115
                } elseif (class_exists('GridFieldOrderableRows')) {
116
                    $gridFieldConfig->addComponent(new GridFieldOrderableRows('DocumentSort'));
117
                }
118
119
                // Don't show which page this is if we're already editing within a page context
120 View Code Duplication
                if (Controller::curr() instanceof CMSPageEditController) {
121
                    $fields->removeByName('PageID');
122
                } else {
123
                    $fields->fieldByName('Root.Main.PageID')->setTitle(_t('DMSDocumentSet.SHOWONPAGE', 'Show on page'));
124
                }
125
126
                // Don't show which page this is if we're already editing within a page context
127 View Code Duplication
                if (Controller::curr() instanceof CMSPageEditController) {
128
                    $fields->removeByName('PageID');
129
                } else {
130
                    $fields->fieldByName('Root.Main.PageID')->setTitle(_t('DMSDocumentSet.SHOWONPAGE', 'Show on page'));
131
                }
132
133
                $gridFieldConfig->getComponentByType('GridFieldDataColumns')
134
                    ->setDisplayFields($self->getDocumentDisplayFields())
135
                    ->setFieldCasting(array('LastEdited' => 'Datetime->Ago'))
136
                    ->setFieldFormatting(
137
                        array(
138
                            'FilenameWithoutID' => '<a target=\'_blank\' class=\'file-url\''
139
                                . ' href=\'$Link\'>$FilenameWithoutID</a>',
140
                            'ManuallyAdded' => function ($value) {
141
                                if ($value) {
142
                                    return _t('DMSDocumentSet.MANUAL', 'Manually');
143
                                }
144
                                return _t('DMSDocumentSet.QUERYBUILDER', 'Query Builder');
145
                            }
146
                        )
147
                    );
148
149
                // Override delete functionality with this class
150
                $gridFieldConfig->getComponentByType('GridFieldDetailForm')
151
                    ->setItemRequestClass('DMSGridFieldDetailForm_ItemRequest');
152
                $gridField = GridField::create(
153
                    'Documents',
154
                    false,
155
                    $self->Documents(),
0 ignored issues
show
The method Documents() does not exist on DMSDocumentSet. Did you maybe mean getDocuments()?

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...
156
                    $gridFieldConfig
157
                );
158
                $gridField->setModelClass('DMSDocument');
159
                $gridField->addExtraClass('documents');
160
161
                $gridFieldConfig->addComponent(
162
                    $addNewButton = new DMSGridFieldAddNewButton('buttons-before-left'),
163
                    'GridFieldExportButton'
164
                );
165
                $addNewButton->setDocumentSetId($self->ID);
166
167
                $fields->removeByName('Documents');
168
                $fields->addFieldsToTab(
169
                    'Root.Main',
170
                    array(
171
                        $gridField,
172
                        HiddenField::create('DMSShortcodeHandlerKey', false, DMS::inst()->getShortcodeHandlerKey())
173
                    )
174
                );
175
                $self->addQueryFields($fields);
176
            }
177
        });
178
        $this->addRequirements();
179
        return parent::getCMSFields();
180
    }
181
182
    /**
183
     * Add required CSS and Javascript requirements for managing documents
184
     *
185
     * @return $this
186
     */
187
    protected function addRequirements()
188
    {
189
        // Javascript to customize the grid field for the DMS document (overriding entwine
190
        // in FRAMEWORK_DIR.'/javascript/GridField.js'
191
        Requirements::javascript(DMS_DIR . '/javascript/DMSGridField.js');
192
        Requirements::css(DMS_DIR . '/dist/css/dmsbundle.css');
193
194
        // Javascript for the link editor pop-up in TinyMCE
195
        Requirements::javascript(DMS_DIR . '/javascript/DocumentHtmlEditorFieldToolbar.js');
196
197
        return $this;
198
    }
199
200
    /**
201
     * Adds the query fields to build the document logic to the DMSDocumentSet.
202
     *
203
     * @param FieldList $fields
204
     */
205
    public function addQueryFields($fields)
206
    {
207
        /** @var DMSDocument $doc */
208
        $doc = singleton('DMSDocument');
209
        /** @var FormField $field */
210
        $dmsDocFields = $doc->scaffoldSearchFields(array('fieldClasses' => true));
211
        $membersMap = Member::get()->map('ID', 'Name')->toArray();
212
        asort($membersMap);
213
214
        foreach ($dmsDocFields as $field) {
215
            if ($field instanceof ListboxField) {
216
                $map = ($field->getName() === 'Tags__ID') ? $doc->getAllTagsMap() : $membersMap;
217
                $field->setMultiple(true)->setSource($map);
218
219
                if ($field->getName() === 'Tags__ID') {
220
                    $field->setRightTitle(
221
                        _t(
222
                            'DMSDocumentSet.TAGS_RIGHT_TITLE',
223
                            'Tags can be set in the taxonomy area, and can be assigned when editing a document.'
224
                        )
225
                    );
226
                }
227
            }
228
        }
229
        $keyValPairs = DMSJsonField::create('KeyValuePairs', $dmsDocFields->toArray());
230
231
        // Now lastly add the sort fields
232
        $sortedBy = FieldGroup::create('SortedBy', array(
233
            DropdownField::create('SortBy', '', array(
234
                'LastEdited'  => 'Last changed',
235
                'Created'     => 'Created',
236
                'Title'       => 'Document title',
237
            ), 'LastEdited'),
238
            DropdownField::create(
239
                'SortByDirection',
240
                '',
241
                array(
242
                    'DESC' => _t('DMSDocumentSet.DIRECTION_DESCENDING', 'Descending'),
243
                    'ASC' => _t('DMSDocumentSet.DIRECTION_ASCENDING', 'Ascending')
244
                ),
245
                'DESC'
246
            ),
247
        ));
248
249
        $sortedBy->setTitle(_t('DMSDocumentSet.SORTED_BY', 'Sort the document set by:'));
250
        $fields->addFieldsToTab(
251
            'Root.QueryBuilder',
252
            array(
253
                LiteralField::create(
254
                    'GridFieldNotice',
255
                    '<p class="message warning">' . _t(
256
                        'DMSDocumentSet.QUERY_BUILDER_NOTICE',
257
                        'The query builder provides the ability to add documents to a document set based on the ' .
258
                        'filters below. Please note that the set will be built using this criteria when you save the ' .
259
                        'form. This set will not be dynamically updated (see the documentation for more information).'
260
                    ) . '</p>'
261
                ),
262
                $keyValPairs,
263
                $sortedBy
264
            )
265
        );
266
    }
267
268
    public function onBeforeWrite()
269
    {
270
        parent::onBeforeWrite();
271
272
        $this->saveLinkedDocuments();
273
    }
274
275
    /**
276
     * Retrieve a list of the documents in this set. An extension hook is provided before the result is returned.
277
     */
278
    public function saveLinkedDocuments()
279
    {
280
        if (empty($this->KeyValuePairs) || !$this->isChanged('KeyValuePairs')) {
281
            return;
282
        }
283
284
        $keyValuesPair = Convert::json2array($this->KeyValuePairs);
285
286
        /** @var DMSDocument $dmsDoc */
287
        $dmsDoc = singleton('DMSDocument');
288
        $context = $dmsDoc->getDefaultSearchContext();
289
290
        $sortBy = $this->SortBy ? $this->SortBy : 'LastEdited';
291
        $sortByDirection = $this->SortByDirection ? $this->SortByDirection : 'DESC';
292
        $sortedBy = sprintf('%s %s', $sortBy, $sortByDirection);
293
294
        /** @var DataList $documents */
295
        $documents = $context->getResults($keyValuesPair, $sortedBy);
0 ignored issues
show
It seems like $keyValuesPair defined by \Convert::json2array($this->KeyValuePairs) on line 284 can also be of type boolean; however, SearchContext::getResults() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
296
        $documents = $this->addEmbargoConditions($documents);
297
        $documents = $this->addQueryBuilderSearchResults($documents);
298
    }
299
300
    /**
301
     * Add embargo date conditions to a search query
302
     *
303
     * @param  DataList $documents
304
     * @return DataList
305
     */
306
    protected function addEmbargoConditions(DataList $documents)
307
    {
308
        $now = SS_Datetime::now()->Rfc2822();
309
310
        return $documents->where(
311
            "\"EmbargoedIndefinitely\" = 0 AND "
312
            . " \"EmbargoedUntilPublished\" = 0 AND "
313
            . "(\"EmbargoedUntilDate\" IS NULL OR "
314
            . "(\"EmbargoedUntilDate\" IS NOT NULL AND '{$now}' >= \"EmbargoedUntilDate\")) AND "
315
            . "\"ExpireAtDate\" IS NULL OR (\"ExpireAtDate\" IS NOT NULL AND '{$now}' < \"ExpireAtDate\")"
316
        );
317
    }
318
319
    /**
320
     * Remove all ManuallyAdded = 0 original results and add in the new documents returned by the search context
321
     *
322
     * @param  DataList $documents
323
     * @return DataList
324
     */
325
    protected function addQueryBuilderSearchResults(DataList $documents)
326
    {
327
        /** @var ManyManyList $originals Documents that belong to just this set. */
328
        $originals = $this->Documents();
0 ignored issues
show
The method Documents() does not exist on DMSDocumentSet. Did you maybe mean getDocuments()?

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...
329
        $originals->removeByFilter('"ManuallyAdded" = 0');
330
331
        foreach ($documents as $document) {
332
            $originals->add($document, array('ManuallyAdded' => 0));
333
        }
334
335
        return $originals;
336
    }
337
338
    /**
339
     * Customise the display fields for the documents GridField
340
     *
341
     * @return array
342
     */
343
    public function getDocumentDisplayFields()
344
    {
345
        return array_merge(
346
            (array) DMSDocument::create()->config()->get('display_fields'),
347
            array('ManuallyAdded' => _t('DMSDocumentSet.ADDEDMETHOD', 'Added'))
348
        );
349
    }
350
351
    protected function validate()
352
    {
353
        $result = parent::validate();
354
355
        if (!$this->getTitle()) {
356
            $result->error(_t('DMSDocumentSet.VALIDATION_NO_TITLE', '\'Title\' is required.'));
357
        }
358
        return $result;
359
    }
360
361 View Code Duplication
    public function canView($member = null)
362
    {
363
        $extended = $this->extendedCan(__FUNCTION__, $member);
364
        if ($extended !== null) {
365
            return $extended;
366
        }
367
        return $this->getGlobalPermission($member);
368
    }
369
370 View Code Duplication
    public function canCreate($member = null)
371
    {
372
        $extended = $this->extendedCan(__FUNCTION__, $member);
373
        if ($extended !== null) {
374
            return $extended;
375
        }
376
        return $this->getGlobalPermission($member);
377
    }
378
379 View Code Duplication
    public function canEdit($member = null)
380
    {
381
        $extended = $this->extendedCan(__FUNCTION__, $member);
382
        if ($extended !== null) {
383
            return $extended;
384
        }
385
        return $this->getGlobalPermission($member);
386
    }
387
388 View Code Duplication
    public function canDelete($member = null)
389
    {
390
        $extended = $this->extendedCan(__FUNCTION__, $member);
391
        if ($extended !== null) {
392
            return $extended;
393
        }
394
        return $this->getGlobalPermission($member);
395
    }
396
397
    /**
398
     * Checks if a then given (or logged in) member is either an ADMIN, SITETREE_EDIT_ALL or has access
399
     * to the DMSDocumentAdmin module, in which case permissions is granted.
400
     *
401
     * @param Member $member
402
     * @return bool
403
     */
404
    public function getGlobalPermission(Member $member = null)
405
    {
406 View Code Duplication
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
407
            $member = Member::currentUser();
408
        }
409
410
        $result = ($member &&
411
            Permission::checkMember(
412
                $member,
413
                array('ADMIN', 'SITETREE_EDIT_ALL', 'CMS_ACCESS_DMSDocumentAdmin')
414
            )
415
        );
416
417
        return (bool) $result;
418
    }
419
}
420