Completed
Push — master ( b4e5c2...6db701 )
by Daniel
03:09
created

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