Completed
Pull Request — master (#168)
by Franco
02:41
created

code/model/DMSDocument.php (1 issue)

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