Completed
Push — master ( b4e5c2...6db701 )
by Daniel
05:37 queued 03:12
created

code/model/DMSDocument.php (1 issue)

Labels
Severity

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