Completed
Pull Request — master (#168)
by Franco
02:31
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 View Code Duplication
        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 View Code Duplication
        if ($member && Permission::checkMember($member, array(
120
                'ADMIN',
121
                'SITETREE_EDIT_ALL',
122
                'SITETREE_VIEW_ALL',
123
            ))
124
        ) {
125
            return true;
126
        }
127
128
        if ($this->isHidden()) {
129
            return false;
130
        }
131
132
        if ($this->CanViewType == 'LoggedInUsers') {
133
            return $member && $member->exists();
134
        }
135
136
        if ($this->CanViewType == 'OnlyTheseUsers' && $this->ViewerGroups()->count()) {
137
            return ($member && $member->inGroups($this->ViewerGroups()));
138
        }
139
140
        return $this->canEdit($member);
141
    }
142
143
    public function canEdit($member = null)
144
    {
145 View Code Duplication
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
146
            $member = Member::currentUser();
147
        }
148
149
        $results = $this->extend('canEdit', $member);
150
151
        if ($results && is_array($results)) {
152
            if (!min($results)) {
153
                return false;
154
            }
155
        }
156
157
        // Do early admin check
158 View Code Duplication
        if ($member && Permission::checkMember(
159
            $member,
160
            array(
161
                    'ADMIN',
162
                    'SITETREE_EDIT_ALL',
163
                    'SITETREE_VIEW_ALL',
164
                )
165
        )
166
        ) {
167
            return true;
168
        }
169
170
        if ($this->CanEditType === 'LoggedInUsers') {
171
            return $member && $member->exists();
172
        }
173
174
        if ($this->CanEditType === 'OnlyTheseUsers' && $this->EditorGroups()->count()) {
175
            return $member && $member->inGroups($this->EditorGroups());
176
        }
177
178
        return ($member && Permission::checkMember($member, array('ADMIN', 'SITETREE_EDIT_ALL')));
179
    }
180
181
    /**
182
     * @param Member $member
183
     *
184
     * @return boolean
185
     */
186
    public function canCreate($member = null)
187
    {
188 View Code Duplication
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
189
            $member = Member::currentUser();
190
        }
191
192
        $results = $this->extend('canCreate', $member);
193
194
        if ($results && is_array($results)) {
195
            if (!min($results)) {
196
                return false;
197
            }
198
        }
199
200
        // Do early admin check
201
        if ($member &&
202
            Permission::checkMember(
203
                $member,
204
                array(
205
                    'CMS_ACCESS_DMSDocumentAdmin',
206
                )
207
            )
208
        ) {
209
            return true;
210
        }
211
212
        return $this->canEdit($member);
213
    }
214
215
    /**
216
     * @param Member $member
217
     *
218
     * @return boolean
219
     */
220
    public function canDelete($member = null)
221
    {
222 View Code Duplication
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
223
            $member = Member::currentUser();
224
        }
225
226
        $results = $this->extend('canDelete', $member);
227
228
        if ($results && is_array($results)) {
229
            if (!min($results)) {
230
                return false;
231
            }
232
        }
233
234
        return $this->canEdit($member);
235
    }
236
237
    /**
238
     * Increase ViewCount by 1, without update any other record fields such as
239
     * LastEdited.
240
     *
241
     * @return DMSDocument
242
     */
243
    public function trackView()
244
    {
245
        if ($this->ID > 0) {
246
            $count = $this->ViewCount + 1;
247
248
            $this->ViewCount = $count;
249
250
            DB::query("UPDATE \"DMSDocument\" SET \"ViewCount\"='$count' WHERE \"ID\"={$this->ID}");
251
        }
252
253
        return $this;
254
    }
255
256
    /**
257
     * Returns a link to download this document from the DMS store.
258
     * Alternatively a basic javascript alert will be shown should the user not have view permissions. An extension
259
     * point for this was also added.
260
     *
261
     * To extend use the following from within an Extension subclass:
262
     *
263
     * <code>
264
     * public function updateGetLink($result)
265
     * {
266
     *     // Do something here
267
     * }
268
     * </code>
269
     *
270
     * @return string
271
     */
272
    public function getLink()
273
    {
274
        $urlSegment = sprintf('%d-%s', $this->ID, URLSegmentFilter::create()->filter($this->getTitle()));
275
        $result = Controller::join_links(Director::baseURL(), 'dmsdocument/' . $urlSegment);
276
        if (!$this->canView()) {
277
            $result = sprintf("javascript:alert('%s')", $this->getPermissionDeniedReason());
278
        }
279
280
        $this->extend('updateGetLink', $result);
281
282
        return $result;
283
    }
284
285
    /**
286
     * @return string
287
     */
288
    public function Link()
289
    {
290
        return $this->getLink();
291
    }
292
293
    /**
294
     * Hides the document, so it does not show up when getByPage($myPage) is
295
     * called (without specifying the $showEmbargoed = true parameter).
296
     *
297
     * This is similar to expire, except that this method should be used to hide
298
     * documents that have not yet gone live.
299
     *
300
     * @param bool $write Save change to the database
301
     *
302
     * @return DMSDocument
303
     */
304
    public function embargoIndefinitely($write = true)
305
    {
306
        $this->EmbargoedIndefinitely = true;
307
308
        if ($write) {
309
            $this->write();
310
        }
311
312
        return $this;
313
    }
314
315
    /**
316
     * Hides the document until any page it is linked to is published
317
     *
318
     * @param bool $write Save change to database
319
     *
320
     * @return DMSDocument
321
     */
322
    public function embargoUntilPublished($write = true)
323
    {
324
        $this->EmbargoedUntilPublished = true;
325
326
        if ($write) {
327
            $this->write();
328
        }
329
330
        return $this;
331
    }
332
333
    /**
334
     * Returns if this is Document is embargoed or expired.
335
     *
336
     * Also, returns if the document should be displayed on the front-end,
337
     * respecting the current reading mode of the site and the embargo status.
338
     *
339
     * I.e. if a document is embargoed until published, then it should still
340
     * show up in draft mode.
341
     *
342
     * @return bool
343
     */
344
    public function isHidden()
345
    {
346
        $hidden = $this->isEmbargoed() || $this->isExpired();
347
        $readingMode = Versioned::get_reading_mode();
348
349
        if ($readingMode == "Stage.Stage") {
350
            if ($this->EmbargoedUntilPublished == true) {
351
                $hidden = false;
352
            }
353
        }
354
355
        return $hidden;
356
    }
357
358
    /**
359
     * Returns if this is Document is embargoed.
360
     *
361
     * @return bool
362
     */
363
    public function isEmbargoed()
364
    {
365
        if (is_object($this->EmbargoedUntilDate)) {
366
            $this->EmbargoedUntilDate = $this->EmbargoedUntilDate->Value;
367
        }
368
369
        $embargoed = false;
370
371
        if ($this->EmbargoedIndefinitely) {
372
            $embargoed = true;
373
        } elseif ($this->EmbargoedUntilPublished) {
374
            $embargoed = true;
375
        } elseif (!empty($this->EmbargoedUntilDate)) {
376
            if (SS_Datetime::now()->Value < $this->EmbargoedUntilDate) {
377
                $embargoed = true;
378
            }
379
        }
380
381
        return $embargoed;
382
    }
383
384
    /**
385
     * Hides the document, so it does not show up when getByPage($myPage) is
386
     * called. Automatically un-hides the Document at a specific date.
387
     *
388
     * @param string $datetime date time value when this Document should expire.
389
     * @param bool $write
390
     *
391
     * @return DMSDocument
392
     */
393 View Code Duplication
    public function embargoUntilDate($datetime, $write = true)
394
    {
395
        $this->EmbargoedUntilDate = DBField::create_field('SS_Datetime', $datetime)->Format('Y-m-d H:i:s');
396
397
        if ($write) {
398
            $this->write();
399
        }
400
401
        return $this;
402
    }
403
404
    /**
405
     * Clears any previously set embargos, so the Document always shows up in
406
     * all queries.
407
     *
408
     * @param bool $write
409
     *
410
     * @return DMSDocument
411
     */
412
    public function clearEmbargo($write = true)
413
    {
414
        $this->EmbargoedIndefinitely = false;
415
        $this->EmbargoedUntilPublished = false;
416
        $this->EmbargoedUntilDate = null;
417
418
        if ($write) {
419
            $this->write();
420
        }
421
422
        return $this;
423
    }
424
425
    /**
426
     * Returns if this is Document is expired.
427
     *
428
     * @return bool
429
     */
430
    public function isExpired()
431
    {
432
        if (is_object($this->ExpireAtDate)) {
433
            $this->ExpireAtDate = $this->ExpireAtDate->Value;
434
        }
435
436
        $expired = false;
437
438
        if (!empty($this->ExpireAtDate)) {
439
            if (SS_Datetime::now()->Value >= $this->ExpireAtDate) {
440
                $expired = true;
441
            }
442
        }
443
444
        return $expired;
445
    }
446
447
    /**
448
     * Hides the document at a specific date, so it does not show up when
449
     * getByPage($myPage) is called.
450
     *
451
     * @param string $datetime date time value when this Document should expire
452
     * @param bool $write
453
     *
454
     * @return DMSDocument
455
     */
456 View Code Duplication
    public function expireAtDate($datetime, $write = true)
457
    {
458
        $this->ExpireAtDate = DBField::create_field('SS_Datetime', $datetime)->Format('Y-m-d H:i:s');
459
460
        if ($write) {
461
            $this->write();
462
        }
463
464
        return $this;
465
    }
466
467
    /**
468
     * Clears any previously set expiry.
469
     *
470
     * @param bool $write
471
     *
472
     * @return DMSDocument
473
     */
474
    public function clearExpiry($write = true)
475
    {
476
        $this->ExpireAtDate = null;
477
478
        if ($write) {
479
            $this->write();
480
        }
481
482
        return $this;
483
    }
484
485
    /**
486
     * Returns a DataList of all previous Versions of this document (check the
487
     * LastEdited date of each object to find the correct one).
488
     *
489
     * If {@link DMSDocument_versions::$enable_versions} is disabled then an
490
     * Exception is thrown
491
     *
492
     * @throws Exception
493
     *
494
     * @return DataList List of Document objects
495
     */
496
    public function getVersions()
497
    {
498
        if (!DMSDocument_versions::$enable_versions) {
499
            throw new Exception("DMSDocument versions are disabled");
500
        }
501
502
        return DMSDocument_versions::get_versions($this);
503
    }
504
505
    /**
506
     * Returns the full filename of the document stored in this object.
507
     *
508
     * @return string
509
     */
510
    public function getFullPath()
511
    {
512
        if ($this->Filename) {
513
            return DMS::inst()->getStoragePath() . DIRECTORY_SEPARATOR
514
                . $this->Folder . DIRECTORY_SEPARATOR . $this->Filename;
515
        }
516
517
        return null;
518
    }
519
520
    /**
521
     * Returns the filename of this asset.
522
     *
523
     * @return string
524
     */
525
    public function getFilename()
526
    {
527
        if ($this->getField('Filename')) {
528
            return $this->getField('Filename');
529
        }
530
        return ASSETS_DIR . '/';
531
    }
532
533
    /**
534
     * @return string
535
     */
536
    public function getName()
537
    {
538
        return $this->getField('Title');
539
    }
540
541
542
    /**
543
     * Returns the filename of a document without the prefix, e.g. 0~filename.jpg -> filename.jpg
544
     *
545
     * @return string
546
     */
547
    public function getFilenameWithoutID()
548
    {
549
        $filenameParts = explode('~', $this->Filename);
550
        $filename = array_pop($filenameParts);
551
552
        return $filename;
553
    }
554
555
    /**
556
     * @return string
557
     */
558
    public function getStorageFolder()
559
    {
560
        return DMS::inst()->getStoragePath() . DIRECTORY_SEPARATOR . DMS::inst()->getStorageFolder($this->ID);
561
    }
562
563
    /**
564
     * Deletes the DMSDocument and its underlying file. Also calls the parent DataObject's delete method in
565
     * order to complete an cascade.
566
     *
567
     * @return void
568
     */
569
    public function delete()
570
    {
571
        // delete the file (and previous versions of files)
572
        $filesToDelete = array();
573
        $storageFolder = $this->getStorageFolder();
574
575
        if (file_exists($storageFolder)) {
576
            if ($handle = opendir($storageFolder)) {
577
                while (false !== ($entry = readdir($handle))) {
578
                    // only delete if filename starts the the relevant ID
579
                    if (strpos($entry, $this->ID.'~') === 0) {
580
                        $filesToDelete[] = $entry;
581
                    }
582
                }
583
584
                closedir($handle);
585
586
                //delete all this files that have the id of this document
587
                foreach ($filesToDelete as $file) {
588
                    $filePath = $storageFolder .DIRECTORY_SEPARATOR . $file;
589
590
                    if (is_file($filePath)) {
591
                        unlink($filePath);
592
                    }
593
                }
594
            }
595
        }
596
597
        // get rid of any versions have saved for this DMSDocument, too
598
        if (DMSDocument_versions::$enable_versions) {
599
            $versions = $this->getVersions();
600
601
            if ($versions->Count() > 0) {
602
                foreach ($versions as $v) {
603
                    $v->delete();
604
                }
605
            }
606
        }
607
608
        return parent::delete();
609
    }
610
611
    /**
612
     * Relate an existing file on the filesystem to the document.
613
     *
614
     * Copies the file to the new destination, as defined in {@link DMS::getStoragePath()}.
615
     *
616
     * @param string $filePath Path to file, relative to webroot.
617
     *
618
     * @return DMSDocument
619
     */
620
    public function storeDocument($filePath)
621
    {
622
        if (empty($this->ID)) {
623
            user_error("Document must be written to database before it can store documents", E_USER_ERROR);
624
        }
625
626
        // calculate all the path to copy the file to
627
        $fromFilename = basename($filePath);
628
        $toFilename = $this->ID. '~' . $fromFilename; //add the docID to the start of the Filename
629
        $toFolder = DMS::inst()->getStorageFolder($this->ID);
630
        $toPath = DMS::inst()->getStoragePath() . DIRECTORY_SEPARATOR . $toFolder . DIRECTORY_SEPARATOR . $toFilename;
631
632
        DMS::inst()->createStorageFolder(DMS::inst()->getStoragePath() . DIRECTORY_SEPARATOR . $toFolder);
633
634
        //copy the file into place
635
        $fromPath = BASE_PATH . DIRECTORY_SEPARATOR . $filePath;
636
637
        //version the existing file (copy it to a new "very specific" filename
638
        if (DMSDocument_versions::$enable_versions) {
639
            DMSDocument_versions::create_version($this);
640
        } else {    //otherwise delete the old document file
641
            $oldPath = $this->getFullPath();
642
            if (file_exists($oldPath)) {
643
                unlink($oldPath);
644
            }
645
        }
646
647
        copy($fromPath, $toPath);   //this will overwrite the existing file (if present)
648
649
        //write the filename of the stored document
650
        $this->Filename = $toFilename;
651
        $this->Folder = strval($toFolder);
652
653
        $extension = pathinfo($this->Filename, PATHINFO_EXTENSION);
654
655
        if (empty($this->Title)) {
656
            // don't overwrite existing document titles
657
            $this->Title = basename($filePath, '.'.$extension);
658
        }
659
660
        $this->write();
661
662
        return $this;
663
    }
664
665
    /**
666
     * Takes a File object or a String (path to a file) and copies it into the
667
     * DMS, replacing the original document file but keeping the rest of the
668
     * document unchanged.
669
     *
670
     * @param File|string $file path to a file to store
671
     *
672
     * @return DMSDocument object that we replaced the file in
673
     */
674
    public function replaceDocument($file)
675
    {
676
        $filePath = DMS::inst()->transformFileToFilePath($file);
677
        $doc = $this->storeDocument($filePath); // replace the document
678
679
        return $doc;
680
    }
681
682
683
    /**
684
     * Return the type of file for the given extension
685
     * on the current file name.
686
     *
687
     * @param string $ext
688
     *
689
     * @return string
690
     */
691
    public static function get_file_type($ext)
692
    {
693
        $types = array(
694
            'gif' => 'GIF image - good for diagrams',
695
            'jpg' => 'JPEG image - good for photos',
696
            'jpeg' => 'JPEG image - good for photos',
697
            'png' => 'PNG image - good general-purpose format',
698
            'ico' => 'Icon image',
699
            'tiff' => 'Tagged image format',
700
            'doc' => 'Word document',
701
            'xls' => 'Excel spreadsheet',
702
            'zip' => 'ZIP compressed file',
703
            'gz' => 'GZIP compressed file',
704
            'dmg' => 'Apple disk image',
705
            'pdf' => 'Adobe Acrobat PDF file',
706
            'mp3' => 'MP3 audio file',
707
            'wav' => 'WAV audo file',
708
            'avi' => 'AVI video file',
709
            'mpg' => 'MPEG video file',
710
            'mpeg' => 'MPEG video file',
711
            'js' => 'Javascript file',
712
            'css' => 'CSS file',
713
            'html' => 'HTML file',
714
            'htm' => 'HTML file'
715
        );
716
717
        return isset($types[$ext]) ? $types[$ext] : $ext;
718
    }
719
720
721
    /**
722
     * Returns the Description field with HTML <br> tags added when there is a
723
     * line break.
724
     *
725
     * @return string
726
     */
727
    public function getDescriptionWithLineBreak()
728
    {
729
        return nl2br($this->getField('Description'));
730
    }
731
732
    /**
733
     * @return FieldList
734
     */
735
    public function getCMSFields()
736
    {
737
        //include JS to handling showing and hiding of bottom "action" tabs
738
        Requirements::javascript(DMS_DIR . '/javascript/DMSDocumentCMSFields.js');
739
        Requirements::css(DMS_DIR . '/dist/css/cmsbundle.css');
740
741
        $fields = new FieldList();  //don't use the automatic scaffolding, it is slow and unnecessary here
742
743
        $extraTasks = '';   //additional text to inject into the list of tasks at the bottom of a DMSDocument CMSfield
744
745
        //get list of shortcode page relations
746
        $relationFinder = new ShortCodeRelationFinder();
747
        $relationList = $relationFinder->getList($this->ID);
748
749
        $fieldsTop = $this->getFieldsForFile($relationList->count());
750
        $fields->add($fieldsTop);
751
752
        $fields->add(TextField::create('Title', _t('DMSDocument.TITLE', 'Title')));
753
        $fields->add(TextareaField::create('Description', _t('DMSDocument.DESCRIPTION', 'Description')));
754
755
        $coverImageField = UploadField::create('CoverImage', _t('DMSDocument.COVERIMAGE', 'Cover Image'));
756
        $coverImageField->getValidator()->setAllowedExtensions(array('jpg', 'jpeg', 'png', 'gif'));
757
        $coverImageField->setConfig('allowedMaxFileNumber', 1);
758
        $fields->add($coverImageField);
759
760
761
        $downloadBehaviorSource = array(
762
            'open' => _t('DMSDocument.OPENINBROWSER', 'Open in browser'),
763
            'download' => _t('DMSDocument.FORCEDOWNLOAD', 'Force download'),
764
        );
765
        $defaultDownloadBehaviour = Config::inst()->get('DMSDocument', 'default_download_behaviour');
766
        if (!isset($downloadBehaviorSource[$defaultDownloadBehaviour])) {
767
            user_error('Default download behaviour "' . $defaultDownloadBehaviour . '" not supported.', E_USER_WARNING);
768
        } else {
769
            $downloadBehaviorSource[$defaultDownloadBehaviour] .= ' (' . _t('DMSDocument.DEFAULT', 'default') . ')';
770
        }
771
772
        $fields->add(
773
            OptionsetField::create(
774
                'DownloadBehavior',
775
                _t('DMSDocument.DOWNLOADBEHAVIOUR', 'Download behavior'),
776
                $downloadBehaviorSource,
777
                $defaultDownloadBehaviour
778
            )
779
            ->setDescription(
780
                'How the visitor will view this file. <strong>Open in browser</strong> '
781
                . 'allows files to be opened in a new tab.'
782
            )
783
        );
784
785
        //create upload field to replace document
786
        $uploadField = new DMSUploadField('ReplaceFile', 'Replace file');
787
        $uploadField->setConfig('allowedMaxFileNumber', 1);
788
        $uploadField->setConfig('downloadTemplateName', 'ss-dmsuploadfield-downloadtemplate');
789
        $uploadField->setRecord($this);
790
791
        $gridFieldConfig = GridFieldConfig::create()->addComponents(
792
            new GridFieldToolbarHeader(),
793
            new GridFieldSortableHeader(),
794
            new GridFieldDataColumns(),
795
            new GridFieldPaginator(30),
796
            //new GridFieldEditButton(),
797
            new GridFieldDetailForm()
798
        );
799
800
        $gridFieldConfig->getComponentByType('GridFieldDataColumns')
801
            ->setDisplayFields(array(
802
                'Title' => 'Title',
803
                'ClassName' => 'Page Type',
804
                'ID' => 'Page ID'
805
            ))
806
            ->setFieldFormatting(array(
807
                'Title' => sprintf(
808
                    '<a class=\"cms-panel-link\" href=\"%s/$ID\">$Title</a>',
809
                    singleton('CMSPageEditController')->Link('show')
810
                )
811
            ));
812
813
        $pagesGrid = GridField::create(
814
            'Pages',
815
            _t('DMSDocument.RelatedPages', 'Related Pages'),
816
            $this->getRelatedPages(),
817
            $gridFieldConfig
818
        );
819
820
        $referencesGrid = GridField::create(
821
            'References',
822
            _t('DMSDocument.RelatedReferences', 'Related References'),
823
            $relationList,
824
            $gridFieldConfig
825
        );
826
827
        if (DMSDocument_versions::$enable_versions) {
828
            $versionsGridFieldConfig = GridFieldConfig::create()->addComponents(
829
                new GridFieldToolbarHeader(),
830
                new GridFieldSortableHeader(),
831
                new GridFieldDataColumns(),
832
                new GridFieldPaginator(30)
833
            );
834
            $versionsGridFieldConfig->getComponentByType('GridFieldDataColumns')
835
                ->setDisplayFields(Config::inst()->get('DMSDocument_versions', 'display_fields'))
836
                ->setFieldFormatting(
837
                    array(
838
                        'FilenameWithoutID' => '<a target="_blank" class="file-url" href="$Link">'
839
                            . '$FilenameWithoutID</a>'
840
                    )
841
                );
842
843
            $versionsGrid =  GridField::create(
844
                'Versions',
845
                _t('DMSDocument.Versions', 'Versions'),
846
                $this->getVersions(),
847
                $versionsGridFieldConfig
848
            );
849
            $this->addActionPanelTask('find-versions', 'Versions');
850
        }
851
852
        $embargoValue = 'None';
853
        if ($this->EmbargoedIndefinitely) {
854
            $embargoValue = 'Indefinitely';
855
        } elseif ($this->EmbargoedUntilPublished) {
856
            $embargoValue = 'Published';
857
        } elseif (!empty($this->EmbargoedUntilDate)) {
858
            $embargoValue = 'Date';
859
        }
860
        $embargo = new OptionsetField(
861
            'Embargo',
862
            _t('DMSDocument.EMBARGO', 'Embargo'),
863
            array(
864
                'None' => _t('DMSDocument.EMBARGO_NONE', 'None'),
865
                'Published' => _t('DMSDocument.EMBARGO_PUBLISHED', 'Hide document until page is published'),
866
                'Indefinitely' => _t('DMSDocument.EMBARGO_INDEFINITELY', 'Hide document indefinitely'),
867
                'Date' => _t('DMSDocument.EMBARGO_DATE', 'Hide until set date')
868
            ),
869
            $embargoValue
870
        );
871
        $embargoDatetime = DatetimeField::create('EmbargoedUntilDate', '');
872
        $embargoDatetime->getDateField()
873
            ->setConfig('showcalendar', true)
874
            ->setConfig('dateformat', 'dd-MM-yyyy')
875
            ->setConfig('datavalueformat', 'dd-MM-yyyy');
876
877
        $expiryValue = 'None';
878
        if (!empty($this->ExpireAtDate)) {
879
            $expiryValue = 'Date';
880
        }
881
        $expiry = new OptionsetField(
882
            'Expiry',
883
            'Expiry',
884
            array(
885
                'None' => 'None',
886
                'Date' => 'Set document to expire on'
887
            ),
888
            $expiryValue
889
        );
890
        $expiryDatetime = DatetimeField::create('ExpireAtDate', '');
891
        $expiryDatetime->getDateField()
892
            ->setConfig('showcalendar', true)
893
            ->setConfig('dateformat', 'dd-MM-yyyy')
894
            ->setConfig('datavalueformat', 'dd-MM-yyyy');
895
896
        // This adds all the actions details into a group.
897
        // Embargo, History, etc to go in here
898
        // These are toggled on and off via the Actions Buttons above
899
        // exit('hit');
900
        $actionsPanel = FieldGroup::create(
901
            FieldGroup::create($embargo, $embargoDatetime)->addExtraClass('embargo'),
902
            FieldGroup::create($expiry, $expiryDatetime)->addExtraClass('expiry'),
903
            FieldGroup::create($uploadField)->addExtraClass('replace'),
904
            FieldGroup::create($pagesGrid)->addExtraClass('find-usage'),
905
            FieldGroup::create($referencesGrid)->addExtraClass('find-references'),
906
            FieldGroup::create($this->getPermissionsActionPanel())->addExtraClass('permissions')
907
        );
908
909
        if ($this->canEdit()) {
910
            $actionsPanel->push(FieldGroup::create($versionsGrid)->addExtraClass('find-versions'));
0 ignored issues
show
The variable $versionsGrid does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
911
            $actionsPanel->push(
912
                FieldGroup::create($this->getRelatedDocumentsGridField())->addExtraClass('find-relateddocuments')
913
            );
914
        } else {
915
            $this->removeActionPanelTask('find-relateddocuments')->removeActionPanelTask('find-versions');
916
        }
917
        $fields->add(LiteralField::create('BottomTaskSelection', $this->getActionTaskHtml()));
918
        $actionsPanel->setName('ActionsPanel');
919
        $actionsPanel->addExtraClass('dmsdocument-actionspanel');
920
        $fields->push($actionsPanel);
921
922
        $this->extend('updateCMSFields', $fields);
923
924
        return $fields;
925
    }
926
927
    /**
928
     * Adds permissions selection fields to a composite field and returns so it can be used in the "actions panel"
929
     *
930
     * @return CompositeField
931
     */
932
    public function getPermissionsActionPanel()
933
    {
934
        $fields = FieldList::create();
935
        $showFields = array(
936
            'CanViewType'  => '',
937
            'ViewerGroups' => 'hide',
938
            'CanEditType'  => '',
939
            'EditorGroups' => 'hide',
940
        );
941
        /** @var SiteTree $siteTree */
942
        $siteTree = singleton('SiteTree');
943
        $settingsFields = $siteTree->getSettingsFields();
944
945
        foreach ($showFields as $name => $extraCss) {
946
            $compositeName = "Root.Settings.$name";
947
            /** @var FormField $field */
948
            if ($field = $settingsFields->fieldByName($compositeName)) {
949
                $field->addExtraClass($extraCss);
950
                $title = str_replace('page', 'document', $field->Title());
951
                $field->setTitle($title);
952
953
                // Remove Inherited source option from DropdownField
954
                if ($field instanceof DropdownField) {
955
                    $options = $field->getSource();
956
                    unset($options['Inherit']);
957
                    $field->setSource($options);
958
                }
959
                $fields->push($field);
960
            }
961
        }
962
963
        $this->extend('updatePermissionsFields', $fields);
964
965
        return CompositeField::create($fields);
966
    }
967
968
    /**
969
     * Return a title to use on the frontend, preferably the "title", otherwise the filename without it's numeric ID
970
     *
971
     * @return string
972
     */
973
    public function getTitle()
974
    {
975
        if ($this->getField('Title')) {
976
            return $this->getField('Title');
977
        }
978
        return $this->FilenameWithoutID;
979
    }
980
981
    public function onBeforeWrite()
982
    {
983
        parent::onBeforeWrite();
984
985
        if (isset($this->Embargo)) {
986
            //set the embargo options from the OptionSetField created in the getCMSFields method
987
            //do not write after clearing the embargo (write happens automatically)
988
            $savedDate = $this->EmbargoedUntilDate;
989
            $this->clearEmbargo(false); // Clear all previous settings and re-apply them on save
990
991
            if ($this->Embargo == 'Published') {
992
                $this->embargoUntilPublished(false);
993
            }
994
            if ($this->Embargo == 'Indefinitely') {
995
                $this->embargoIndefinitely(false);
996
            }
997
            if ($this->Embargo == 'Date') {
998
                $this->embargoUntilDate($savedDate, false);
999
            }
1000
        }
1001
1002
        if (isset($this->Expiry)) {
1003
            if ($this->Expiry == 'Date') {
1004
                $this->expireAtDate($this->ExpireAtDate, false);
1005
            } else {
1006
                $this->clearExpiry(false);
1007
            } // Clear all previous settings
1008
        }
1009
1010
        // Set user fields
1011
        if ($currentUserID = Member::currentUserID()) {
1012
            if (!$this->CreatedByID) {
1013
                $this->CreatedByID = $currentUserID;
1014
            }
1015
            $this->LastEditedByID = $currentUserID;
1016
        }
1017
    }
1018
1019
    /**
1020
     * Return the relative URL of an icon for the file type, based on the
1021
     * {@link appCategory()} value.
1022
     *
1023
     * Images are searched for in "dms/images/app_icons/".
1024
     *
1025
     * @return string
1026
     */
1027
    public function Icon($ext)
1028
    {
1029
        if (!Director::fileExists(DMS_DIR."/images/app_icons/{$ext}_32.png")) {
1030
            $ext = File::get_app_category($ext);
1031
        }
1032
1033
        if (!Director::fileExists(DMS_DIR."/images/app_icons/{$ext}_32.png")) {
1034
            $ext = "generic";
1035
        }
1036
1037
        return DMS_DIR."/images/app_icons/{$ext}_32.png";
1038
    }
1039
1040
    /**
1041
     * Return the extension of the file associated with the document
1042
     *
1043
     * @return string
1044
     */
1045
    public function getExtension()
1046
    {
1047
        return strtolower(pathinfo($this->Filename, PATHINFO_EXTENSION));
1048
    }
1049
1050
    /**
1051
     * @return string
1052
     */
1053
    public function getSize()
1054
    {
1055
        $size = $this->getAbsoluteSize();
1056
        return ($size) ? File::format_size($size) : false;
1057
    }
1058
1059
    /**
1060
     * Return the size of the file associated with the document.
1061
     *
1062
     * @return string
1063
     */
1064
    public function getAbsoluteSize()
1065
    {
1066
        return file_exists($this->getFullPath()) ? filesize($this->getFullPath()) : null;
1067
    }
1068
1069
    /**
1070
     * An alias to DMSDocument::getSize()
1071
     *
1072
     * @return string
1073
     */
1074
    public function getFileSizeFormatted()
1075
    {
1076
        return $this->getSize();
1077
    }
1078
1079
1080
    /**
1081
     * @return FieldList
1082
     */
1083
    protected function getFieldsForFile($relationListCount)
1084
    {
1085
        $extension = $this->getExtension();
1086
1087
        $previewField = new LiteralField(
1088
            "ImageFull",
1089
            "<img id='thumbnailImage' class='thumbnail-preview' src='{$this->Icon($extension)}?r="
1090
            . rand(1, 100000) . "' alt='{$this->Title}' />\n"
1091
        );
1092
1093
        //count the number of pages this document is published on
1094
        $publishedOnCount = $this->getRelatedPages()->count();
1095
        $publishedOnValue = "$publishedOnCount pages";
1096
        if ($publishedOnCount == 1) {
1097
            $publishedOnValue = "$publishedOnCount page";
1098
        }
1099
1100
        $relationListCountValue = "$relationListCount pages";
1101
        if ($relationListCount == 1) {
1102
            $relationListCountValue = "$relationListCount page";
1103
        }
1104
1105
        $fields = new FieldGroup(
1106
            $filePreview = CompositeField::create(
1107
                CompositeField::create(
1108
                    $previewField
1109
                )->setName("FilePreviewImage")->addExtraClass('cms-file-info-preview'),
1110
                CompositeField::create(
1111
                    CompositeField::create(
1112
                        new ReadonlyField("ID", "ID number". ':', $this->ID),
1113
                        new ReadonlyField(
1114
                            "FileType",
1115
                            _t('AssetTableField.TYPE', 'File type') . ':',
1116
                            self::get_file_type($extension)
1117
                        ),
1118
                        new ReadonlyField(
1119
                            "Size",
1120
                            _t('AssetTableField.SIZE', 'File size') . ':',
1121
                            $this->getFileSizeFormatted()
1122
                        ),
1123
                        $urlField = new ReadonlyField(
1124
                            'ClickableURL',
1125
                            _t('AssetTableField.URL', 'URL'),
1126
                            sprintf(
1127
                                '<a href="%s" target="_blank" class="file-url">%s</a>',
1128
                                $this->getLink(),
1129
                                $this->getLink()
1130
                            )
1131
                        ),
1132
                        new ReadonlyField("FilenameWithoutIDField", "Filename". ':', $this->getFilenameWithoutID()),
1133
                        new DateField_Disabled(
1134
                            "Created",
1135
                            _t('AssetTableField.CREATED', 'First uploaded') . ':',
1136
                            $this->Created
1137
                        ),
1138
                        new DateField_Disabled(
1139
                            "LastEdited",
1140
                            _t('AssetTableField.LASTEDIT', 'Last changed') . ':',
1141
                            $this->LastEdited
1142
                        ),
1143
                        new ReadonlyField("PublishedOn", "Published on". ':', $publishedOnValue),
1144
                        new ReadonlyField("ReferencedOn", "Referenced on". ':', $relationListCountValue),
1145
                        new ReadonlyField("ViewCount", "View count". ':', $this->ViewCount)
1146
                    )->setName('FilePreviewDataFields')
1147
                )->setName("FilePreviewData")->addExtraClass('cms-file-info-data')
1148
            )->setName("FilePreview")->addExtraClass('cms-file-info')
1149
        );
1150
1151
        $fields->addExtraClass('dmsdocument-documentdetails');
1152
        $urlField->dontEscape = true;
1153
1154
        $this->extend('updateFieldsForFile', $fields);
1155
1156
        return $fields;
1157
    }
1158
1159
    /**
1160
     * Takes a file and adds it to the DMSDocument storage, replacing the
1161
     * current file.
1162
     *
1163
     * @param File $file
1164
     *
1165
     * @return $this
1166
     */
1167
    public function ingestFile($file)
1168
    {
1169
        $this->replaceDocument($file);
1170
        $file->delete();
1171
1172
        return $this;
1173
    }
1174
1175
    /**
1176
     * Get a data list of documents related to this document
1177
     *
1178
     * @return DataList
1179
     */
1180
    public function getRelatedDocuments()
1181
    {
1182
        $documents = $this->RelatedDocuments();
1183
1184
        $this->extend('updateRelatedDocuments', $documents);
1185
1186
        return $documents;
1187
    }
1188
1189
    /**
1190
     * Get a list of related pages for this document by going through the associated document sets
1191
     *
1192
     * @return ArrayList
1193
     */
1194
    public function getRelatedPages()
1195
    {
1196
        $pages = ArrayList::create();
1197
1198
        foreach ($this->Sets() as $documentSet) {
1199
            /** @var DocumentSet $documentSet */
1200
            $pages->add($documentSet->Page());
1201
        }
1202
        $pages->removeDuplicates();
1203
1204
        $this->extend('updateRelatedPages', $pages);
1205
1206
        return $pages;
1207
    }
1208
1209
    /**
1210
     * Get a GridField for managing related documents
1211
     *
1212
     * @return GridField
1213
     */
1214
    protected function getRelatedDocumentsGridField()
1215
    {
1216
        $gridField = GridField::create(
1217
            'RelatedDocuments',
1218
            _t('DMSDocument.RELATEDDOCUMENTS', 'Related Documents'),
1219
            $this->RelatedDocuments(),
1220
            new GridFieldConfig_RelationEditor
1221
        );
1222
1223
        $gridField->getConfig()->removeComponentsByType('GridFieldAddNewButton');
1224
        // Move the autocompleter to the left
1225
        $gridField->getConfig()->removeComponentsByType('GridFieldAddExistingAutocompleter');
1226
        $gridField->getConfig()->addComponent(
1227
            $addExisting = new GridFieldAddExistingAutocompleter('buttons-before-left')
1228
        );
1229
1230
        // Ensure that current document doesn't get returned in the autocompleter
1231
        $addExisting->setSearchList($this->getRelatedDocumentsForAutocompleter());
1232
1233
        // Restrict search fields to specific fields only
1234
        $addExisting->setSearchFields(array('Title:PartialMatch', 'Filename:PartialMatch'));
1235
        $addExisting->setResultsFormat('$Filename');
1236
1237
        $this->extend('updateRelatedDocumentsGridField', $gridField);
1238
        return $gridField;
1239
    }
1240
1241
    /**
1242
     * Get the list of documents to show in "related documents". This can be modified via the extension point, for
1243
     * example if you wanted to exclude embargoed documents or something similar.
1244
     *
1245
     * @return DataList
1246
     */
1247
    protected function getRelatedDocumentsForAutocompleter()
1248
    {
1249
        $documents = DMSDocument::get()->exclude('ID', $this->ID);
1250
        $this->extend('updateRelatedDocumentsForAutocompleter', $documents);
1251
        return $documents;
1252
    }
1253
1254
    /**
1255
     * Checks at least one group is selected if CanViewType || CanEditType == 'OnlyTheseUsers'
1256
     *
1257
     * @return ValidationResult
1258
     */
1259
    protected function validate()
1260
    {
1261
        $valid = parent::validate();
1262
1263
        if ($this->CanViewType == 'OnlyTheseUsers' && !$this->ViewerGroups()->count()) {
1264
            $valid->error(
1265
                _t(
1266
                    'DMSDocument.VALIDATIONERROR_NOVIEWERSELECTED',
1267
                    "Selecting 'Only these people' from a viewers list needs at least one group selected."
1268
                )
1269
            );
1270
        }
1271
1272
        if ($this->CanEditType == 'OnlyTheseUsers' && !$this->EditorGroups()->count()) {
1273
            $valid->error(
1274
                _t(
1275
                    'DMSDocument.VALIDATIONERROR_NOEDITORSELECTED',
1276
                    "Selecting 'Only these people' from a editors list needs at least one group selected."
1277
                )
1278
            );
1279
        }
1280
1281
        return $valid;
1282
    }
1283
1284
    /**
1285
     * Returns a reason as to why this document cannot be viewed.
1286
     *
1287
     * @return string
1288
     */
1289
    public function getPermissionDeniedReason()
1290
    {
1291
        $result = '';
1292
1293
        if ($this->CanViewType == 'LoggedInUsers') {
1294
            $result = _t('DMSDocument.PERMISSIONDENIEDREASON_LOGINREQUIRED', 'Please log in to view this document');
1295
        }
1296
1297
        if ($this->CanViewType == 'OnlyTheseUsers') {
1298
            $result = _t(
1299
                'DMSDocument.PERMISSIONDENIEDREASON_NOTAUTHORISED',
1300
                'You are not authorised to view this document'
1301
            );
1302
        }
1303
1304
        return $result;
1305
    }
1306
1307
    /**
1308
     * Add an "action panel" task
1309
     *
1310
     * @param  string $panelKey
1311
     * @param  string $title
1312
     * @return $this
1313
     */
1314
    public function addActionPanelTask($panelKey, $title)
1315
    {
1316
        $this->actionTasks[$panelKey] = $title;
1317
        return $this;
1318
    }
1319
1320
    /**
1321
     * Returns a HTML representation of the action tasks for the CMS
1322
     *
1323
     * @return string
1324
     */
1325
    public function getActionTaskHtml()
1326
    {
1327
        $html = '<div class="field dmsdocment-actions">'
1328
            . '<label class="left">' . _t('DMSDocument.ACTIONS_LABEL', 'Actions') . '</label>'
1329
            . '<ul>';
1330
1331
        foreach ($this->actionTasks as $panelKey => $title) {
1332
            $html .= '<li class="ss-ui-button dmsdocument-action" data-panel="' . $panelKey . '">'
1333
                . _t('DMSDocument.ACTION_' . strtoupper($panelKey), $title)
1334
                . '</li>';
1335
        }
1336
1337
        $html .= '</ul></div>';
1338
1339
        return $html;
1340
    }
1341
1342
    /**
1343
     * Removes an "action panel" tasks
1344
     *
1345
     * @param  string $panelKey
1346
     * @return $this
1347
     */
1348
    public function removeActionPanelTask($panelKey)
1349
    {
1350
        if (array_key_exists($panelKey, $this->actionTasks)) {
1351
            unset($this->actionTasks[$panelKey]);
1352
        }
1353
        return $this;
1354
    }
1355
}
1356