Completed
Push — master ( 1bceff...ab9d36 )
by Robbie
01:47
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
        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()) || $this->canEdit($member));
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
        if ($member && Permission::checkMember($member, array('ADMIN','SITETREE_EDIT_ALL'))) {
159
            return true;
160
        }
161
162
        if ($this->CanEditType === 'LoggedInUsers') {
163
            return $member && $member->exists();
164
        }
165
166
        if ($this->CanEditType === 'OnlyTheseUsers' && $this->EditorGroups()->count()) {
167
            return $member && $member->inGroups($this->EditorGroups());
168
        }
169
170
        return false;
171
    }
172
173
    /**
174
     * @param Member $member
175
     *
176
     * @return boolean
177
     */
178
    public function canCreate($member = null)
179
    {
180 View Code Duplication
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
181
            $member = Member::currentUser();
182
        }
183
184
        $results = $this->extend('canCreate', $member);
185
186
        if ($results && is_array($results)) {
187
            if (!min($results)) {
188
                return false;
189
            }
190
        }
191
192
        // Do early admin check
193
        if ($member &&
194
            Permission::checkMember($member, array('CMS_ACCESS_DMSDocumentAdmin'))
195
        ) {
196
            return true;
197
        }
198
199
        return $this->canEdit($member);
200
    }
201
202
    /**
203
     * @param Member $member
204
     *
205
     * @return boolean
206
     */
207
    public function canDelete($member = null)
208
    {
209 View Code Duplication
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
210
            $member = Member::currentUser();
211
        }
212
213
        $results = $this->extend('canDelete', $member);
214
215
        if ($results && is_array($results)) {
216
            if (!min($results)) {
217
                return false;
218
            }
219
        }
220
221
        return $this->canEdit($member);
222
    }
223
224
    /**
225
     * Increase ViewCount by 1, without update any other record fields such as
226
     * LastEdited.
227
     *
228
     * @return DMSDocument
229
     */
230
    public function trackView()
231
    {
232
        if ($this->ID > 0) {
233
            $count = $this->ViewCount + 1;
234
235
            $this->ViewCount = $count;
236
237
            DB::query("UPDATE \"DMSDocument\" SET \"ViewCount\"='$count' WHERE \"ID\"={$this->ID}");
238
            
239
            $this->extend('trackView');
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
        $embargoValue = 'None';
842
        if ($this->EmbargoedIndefinitely) {
843
            $embargoValue = 'Indefinitely';
844
        } elseif ($this->EmbargoedUntilPublished) {
845
            $embargoValue = 'Published';
846
        } elseif (!empty($this->EmbargoedUntilDate)) {
847
            $embargoValue = 'Date';
848
        }
849
        $embargo = new OptionsetField(
850
            'Embargo',
851
            _t('DMSDocument.EMBARGO', 'Embargo'),
852
            array(
853
                'None' => _t('DMSDocument.EMBARGO_NONE', 'None'),
854
                'Published' => _t('DMSDocument.EMBARGO_PUBLISHED', 'Hide document until page is published'),
855
                'Indefinitely' => _t('DMSDocument.EMBARGO_INDEFINITELY', 'Hide document indefinitely'),
856
                'Date' => _t('DMSDocument.EMBARGO_DATE', 'Hide until set date')
857
            ),
858
            $embargoValue
859
        );
860
        $embargoDatetime = DatetimeField::create('EmbargoedUntilDate', '');
861
        $embargoDatetime->getDateField()
862
            ->setConfig('showcalendar', true)
863
            ->setConfig('dateformat', 'dd-MM-yyyy')
864
            ->setConfig('datavalueformat', 'dd-MM-yyyy');
865
866
        $expiryValue = 'None';
867
        if (!empty($this->ExpireAtDate)) {
868
            $expiryValue = 'Date';
869
        }
870
        $expiry = new OptionsetField(
871
            'Expiry',
872
            'Expiry',
873
            array(
874
                'None' => 'None',
875
                'Date' => 'Set document to expire on'
876
            ),
877
            $expiryValue
878
        );
879
        $expiryDatetime = DatetimeField::create('ExpireAtDate', '');
880
        $expiryDatetime->getDateField()
881
            ->setConfig('showcalendar', true)
882
            ->setConfig('dateformat', 'dd-MM-yyyy')
883
            ->setConfig('datavalueformat', 'dd-MM-yyyy');
884
885
        // This adds all the actions details into a group.
886
        // Embargo, History, etc to go in here
887
        // These are toggled on and off via the Actions Buttons above
888
        // exit('hit');
889
        $actionsPanel = FieldGroup::create(
890
            FieldGroup::create($embargo, $embargoDatetime)->addExtraClass('embargo'),
891
            FieldGroup::create($expiry, $expiryDatetime)->addExtraClass('expiry'),
892
            FieldGroup::create($uploadField)->addExtraClass('replace'),
893
            FieldGroup::create($pagesGrid)->addExtraClass('find-usage'),
894
            FieldGroup::create($referencesGrid)->addExtraClass('find-references'),
895
            FieldGroup::create($this->getPermissionsActionPanel())->addExtraClass('permissions')
896
        );
897
898
        if ($this->canEdit()) {
899
            $actionsPanel->push(FieldGroup::create($versionsGrid)->addExtraClass('find-versions'));
900
            $actionsPanel->push(
901
                FieldGroup::create($this->getRelatedDocumentsGridField())->addExtraClass('find-relateddocuments')
902
            );
903
        } else {
904
            $this->removeActionPanelTask('find-relateddocuments')->removeActionPanelTask('find-versions');
905
        }
906
        $fields->add(LiteralField::create('BottomTaskSelection', $this->getActionTaskHtml()));
907
        $actionsPanel->setName('ActionsPanel');
908
        $actionsPanel->addExtraClass('dmsdocument-actionspanel');
909
        $fields->push($actionsPanel);
910
911
        $this->extend('updateCMSFields', $fields);
912
913
        return $fields;
914
    }
915
916
    /**
917
     * Adds permissions selection fields to a composite field and returns so it can be used in the "actions panel"
918
     *
919
     * @return CompositeField
920
     */
921
    public function getPermissionsActionPanel()
922
    {
923
        $fields = FieldList::create();
924
        $showFields = array(
925
            'CanViewType'  => '',
926
            'ViewerGroups' => 'hide',
927
            'CanEditType'  => '',
928
            'EditorGroups' => 'hide',
929
        );
930
        /** @var SiteTree $siteTree */
931
        $siteTree = singleton('SiteTree');
932
        $settingsFields = $siteTree->getSettingsFields();
933
934
        foreach ($showFields as $name => $extraCss) {
935
            $compositeName = "Root.Settings.$name";
936
            /** @var FormField $field */
937
            if ($field = $settingsFields->fieldByName($compositeName)) {
938
                $field->addExtraClass($extraCss);
939
                $title = str_replace('page', 'document', $field->Title());
940
                $field->setTitle($title);
941
942
                // Remove Inherited source option from DropdownField
943
                if ($field instanceof DropdownField) {
944
                    $options = $field->getSource();
945
                    unset($options['Inherit']);
946
                    $field->setSource($options);
947
                }
948
                $fields->push($field);
949
            }
950
        }
951
952
        $this->extend('updatePermissionsFields', $fields);
953
954
        return CompositeField::create($fields);
955
    }
956
957
    /**
958
     * Return a title to use on the frontend, preferably the "title", otherwise the filename without it's numeric ID
959
     *
960
     * @return string
961
     */
962
    public function getTitle()
963
    {
964
        if ($this->getField('Title')) {
965
            return $this->getField('Title');
966
        }
967
        return $this->FilenameWithoutID;
968
    }
969
970
    public function onBeforeWrite()
971
    {
972
        parent::onBeforeWrite();
973
974
        if (isset($this->Embargo)) {
975
            //set the embargo options from the OptionSetField created in the getCMSFields method
976
            //do not write after clearing the embargo (write happens automatically)
977
            $savedDate = $this->EmbargoedUntilDate;
978
            $this->clearEmbargo(false); // Clear all previous settings and re-apply them on save
979
980
            if ($this->Embargo == 'Published') {
981
                $this->embargoUntilPublished(false);
982
            }
983
            if ($this->Embargo == 'Indefinitely') {
984
                $this->embargoIndefinitely(false);
985
            }
986
            if ($this->Embargo == 'Date') {
987
                $this->embargoUntilDate($savedDate, false);
988
            }
989
        }
990
991
        if (isset($this->Expiry)) {
992
            if ($this->Expiry == 'Date') {
993
                $this->expireAtDate($this->ExpireAtDate, false);
994
            } else {
995
                $this->clearExpiry(false);
996
            } // Clear all previous settings
997
        }
998
999
        // Set user fields
1000
        if ($currentUserID = Member::currentUserID()) {
1001
            if (!$this->CreatedByID) {
1002
                $this->CreatedByID = $currentUserID;
1003
            }
1004
            $this->LastEditedByID = $currentUserID;
1005
        }
1006
    }
1007
1008
    /**
1009
     * Return the relative URL of an icon for the file type, based on the
1010
     * {@link appCategory()} value.
1011
     *
1012
     * Images are searched for in "dms/images/app_icons/".
1013
     *
1014
     * @return string
1015
     */
1016
    public function Icon($ext)
1017
    {
1018
        if (!Director::fileExists(DMS_DIR."/images/app_icons/{$ext}_32.png")) {
1019
            $ext = File::get_app_category($ext);
1020
        }
1021
1022
        if (!Director::fileExists(DMS_DIR."/images/app_icons/{$ext}_32.png")) {
1023
            $ext = "generic";
1024
        }
1025
1026
        return DMS_DIR."/images/app_icons/{$ext}_32.png";
1027
    }
1028
1029
    /**
1030
     * Return the extension of the file associated with the document
1031
     *
1032
     * @return string
1033
     */
1034
    public function getExtension()
1035
    {
1036
        return strtolower(pathinfo($this->Filename, PATHINFO_EXTENSION));
1037
    }
1038
1039
    /**
1040
     * @return string
1041
     */
1042
    public function getSize()
1043
    {
1044
        $size = $this->getAbsoluteSize();
1045
        return ($size) ? File::format_size($size) : false;
1046
    }
1047
1048
    /**
1049
     * Return the size of the file associated with the document.
1050
     *
1051
     * @return string
1052
     */
1053
    public function getAbsoluteSize()
1054
    {
1055
        return file_exists($this->getFullPath()) ? filesize($this->getFullPath()) : null;
1056
    }
1057
1058
    /**
1059
     * An alias to DMSDocument::getSize()
1060
     *
1061
     * @return string
1062
     */
1063
    public function getFileSizeFormatted()
1064
    {
1065
        return $this->getSize();
1066
    }
1067
1068
1069
    /**
1070
     * @return FieldList
1071
     */
1072
    protected function getFieldsForFile($relationListCount)
1073
    {
1074
        $extension = $this->getExtension();
1075
1076
        $previewField = new LiteralField(
1077
            "ImageFull",
1078
            "<img id='thumbnailImage' class='thumbnail-preview' src='{$this->Icon($extension)}?r="
1079
            . rand(1, 100000) . "' alt='{$this->Title}' />\n"
1080
        );
1081
1082
        //count the number of pages this document is published on
1083
        $publishedOnCount = $this->getRelatedPages()->count();
1084
        $publishedOnValue = "$publishedOnCount pages";
1085
        if ($publishedOnCount == 1) {
1086
            $publishedOnValue = "$publishedOnCount page";
1087
        }
1088
1089
        $relationListCountValue = "$relationListCount pages";
1090
        if ($relationListCount == 1) {
1091
            $relationListCountValue = "$relationListCount page";
1092
        }
1093
1094
        $fields = new FieldGroup(
1095
            $filePreview = CompositeField::create(
1096
                CompositeField::create(
1097
                    $previewField
1098
                )->setName("FilePreviewImage")->addExtraClass('cms-file-info-preview'),
1099
                CompositeField::create(
1100
                    CompositeField::create(
1101
                        new ReadonlyField("ID", "ID number". ':', $this->ID),
1102
                        new ReadonlyField(
1103
                            "FileType",
1104
                            _t('AssetTableField.TYPE', 'File type') . ':',
1105
                            self::get_file_type($extension)
1106
                        ),
1107
                        new ReadonlyField(
1108
                            "Size",
1109
                            _t('AssetTableField.SIZE', 'File size') . ':',
1110
                            $this->getFileSizeFormatted()
1111
                        ),
1112
                        $urlField = new ReadonlyField(
1113
                            'ClickableURL',
1114
                            _t('AssetTableField.URL', 'URL'),
1115
                            sprintf(
1116
                                '<a href="%s" target="_blank" class="file-url">%s</a>',
1117
                                $this->getLink(),
1118
                                $this->getLink()
1119
                            )
1120
                        ),
1121
                        new ReadonlyField("FilenameWithoutIDField", "Filename". ':', $this->getFilenameWithoutID()),
1122
                        new DateField_Disabled(
1123
                            "Created",
1124
                            _t('AssetTableField.CREATED', 'First uploaded') . ':',
1125
                            $this->Created
1126
                        ),
1127
                        new DateField_Disabled(
1128
                            "LastEdited",
1129
                            _t('AssetTableField.LASTEDIT', 'Last changed') . ':',
1130
                            $this->LastEdited
1131
                        ),
1132
                        new ReadonlyField("PublishedOn", "Published on". ':', $publishedOnValue),
1133
                        new ReadonlyField("ReferencedOn", "Referenced on". ':', $relationListCountValue),
1134
                        new ReadonlyField("ViewCount", "View count". ':', $this->ViewCount)
1135
                    )->setName('FilePreviewDataFields')
1136
                )->setName("FilePreviewData")->addExtraClass('cms-file-info-data')
1137
            )->setName("FilePreview")->addExtraClass('cms-file-info')
1138
        );
1139
1140
        $fields->addExtraClass('dmsdocument-documentdetails');
1141
        $urlField->dontEscape = true;
1142
1143
        $this->extend('updateFieldsForFile', $fields);
1144
1145
        return $fields;
1146
    }
1147
1148
    /**
1149
     * Takes a file and adds it to the DMSDocument storage, replacing the
1150
     * current file.
1151
     *
1152
     * @param File $file
1153
     *
1154
     * @return $this
1155
     */
1156
    public function ingestFile($file)
1157
    {
1158
        $this->replaceDocument($file);
1159
        $file->delete();
1160
1161
        return $this;
1162
    }
1163
1164
    /**
1165
     * Get a data list of documents related to this document
1166
     *
1167
     * @return DataList
1168
     */
1169
    public function getRelatedDocuments()
1170
    {
1171
        $documents = $this->RelatedDocuments();
1172
1173
        $this->extend('updateRelatedDocuments', $documents);
1174
1175
        return $documents;
1176
    }
1177
1178
    /**
1179
     * Get a list of related pages for this document by going through the associated document sets
1180
     *
1181
     * @return ArrayList
1182
     */
1183
    public function getRelatedPages()
1184
    {
1185
        $pages = ArrayList::create();
1186
1187
        foreach ($this->Sets() as $documentSet) {
1188
            /** @var DocumentSet $documentSet */
1189
            $pages->add($documentSet->Page());
1190
        }
1191
        $pages->removeDuplicates();
1192
1193
        $this->extend('updateRelatedPages', $pages);
1194
1195
        return $pages;
1196
    }
1197
1198
    /**
1199
     * Get a GridField for managing related documents
1200
     *
1201
     * @return GridField
1202
     */
1203
    protected function getRelatedDocumentsGridField()
1204
    {
1205
        $gridField = GridField::create(
1206
            'RelatedDocuments',
1207
            _t('DMSDocument.RELATEDDOCUMENTS', 'Related Documents'),
1208
            $this->RelatedDocuments(),
1209
            new GridFieldConfig_RelationEditor
1210
        );
1211
1212
        $gridFieldConfig = $gridField->getConfig();
1213
        $gridFieldConfig->removeComponentsByType('GridFieldEditButton');
1214
        $gridFieldConfig->addComponent(new DMSGridFieldEditButton(), 'GridFieldDeleteAction');
1215
1216
        $gridField->getConfig()->removeComponentsByType('GridFieldAddNewButton');
1217
        // Move the autocompleter to the left
1218
        $gridField->getConfig()->removeComponentsByType('GridFieldAddExistingAutocompleter');
1219
        $gridField->getConfig()->addComponent(
1220
            $addExisting = new GridFieldAddExistingAutocompleter('buttons-before-left')
1221
        );
1222
1223
        // Ensure that current document doesn't get returned in the autocompleter
1224
        $addExisting->setSearchList($this->getRelatedDocumentsForAutocompleter());
1225
1226
        // Restrict search fields to specific fields only
1227
        $addExisting->setSearchFields(array('Title:PartialMatch', 'Filename:PartialMatch'));
1228
        $addExisting->setResultsFormat('$Filename');
1229
1230
        $this->extend('updateRelatedDocumentsGridField', $gridField);
1231
        return $gridField;
1232
    }
1233
1234
    /**
1235
     * Get the list of documents to show in "related documents". This can be modified via the extension point, for
1236
     * example if you wanted to exclude embargoed documents or something similar.
1237
     *
1238
     * @return DataList
1239
     */
1240
    protected function getRelatedDocumentsForAutocompleter()
1241
    {
1242
        $documents = DMSDocument::get()->exclude('ID', $this->ID);
1243
        $this->extend('updateRelatedDocumentsForAutocompleter', $documents);
1244
        return $documents;
1245
    }
1246
1247
    /**
1248
     * Checks at least one group is selected if CanViewType || CanEditType == 'OnlyTheseUsers'
1249
     *
1250
     * @return ValidationResult
1251
     */
1252
    protected function validate()
1253
    {
1254
        $valid = parent::validate();
1255
1256
        if ($this->CanViewType == 'OnlyTheseUsers' && !$this->ViewerGroups()->count()) {
1257
            $valid->error(
1258
                _t(
1259
                    'DMSDocument.VALIDATIONERROR_NOVIEWERSELECTED',
1260
                    "Selecting 'Only these people' from a viewers list needs at least one group selected."
1261
                )
1262
            );
1263
        }
1264
1265
        if ($this->CanEditType == 'OnlyTheseUsers' && !$this->EditorGroups()->count()) {
1266
            $valid->error(
1267
                _t(
1268
                    'DMSDocument.VALIDATIONERROR_NOEDITORSELECTED',
1269
                    "Selecting 'Only these people' from a editors list needs at least one group selected."
1270
                )
1271
            );
1272
        }
1273
1274
        return $valid;
1275
    }
1276
1277
    /**
1278
     * Returns a reason as to why this document cannot be viewed.
1279
     *
1280
     * @return string
1281
     */
1282
    public function getPermissionDeniedReason()
1283
    {
1284
        $result = '';
1285
1286
        if ($this->CanViewType == 'LoggedInUsers') {
1287
            $result = _t('DMSDocument.PERMISSIONDENIEDREASON_LOGINREQUIRED', 'Please log in to view this document');
1288
        }
1289
1290
        if ($this->CanViewType == 'OnlyTheseUsers') {
1291
            $result = _t(
1292
                'DMSDocument.PERMISSIONDENIEDREASON_NOTAUTHORISED',
1293
                'You are not authorised to view this document'
1294
            );
1295
        }
1296
1297
        return $result;
1298
    }
1299
1300
    /**
1301
     * Add an "action panel" task
1302
     *
1303
     * @param  string $panelKey
1304
     * @param  string $title
1305
     * @return $this
1306
     */
1307
    public function addActionPanelTask($panelKey, $title)
1308
    {
1309
        $this->actionTasks[$panelKey] = $title;
1310
        return $this;
1311
    }
1312
1313
    /**
1314
     * Returns a HTML representation of the action tasks for the CMS
1315
     *
1316
     * @return string
1317
     */
1318
    public function getActionTaskHtml()
1319
    {
1320
        $html = '<div class="field dmsdocment-actions">'
1321
            . '<label class="left">' . _t('DMSDocument.ACTIONS_LABEL', 'Actions') . '</label>'
1322
            . '<ul>';
1323
1324
        foreach ($this->actionTasks as $panelKey => $title) {
1325
            $panelKey = Convert::raw2xml($panelKey);
1326
            $title = Convert::raw2xml($title);
1327
1328
            $html .= '<li class="ss-ui-button dmsdocument-action" data-panel="' . $panelKey . '">'
1329
                . _t('DMSDocument.ACTION_' . strtoupper($panelKey), $title)
0 ignored issues
show
It seems like $title defined by \Convert::raw2xml($title) on line 1326 can also be of type array; however, _t() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1330
                . '</li>';
1331
        }
1332
1333
        $html .= '</ul></div>';
1334
1335
        return $html;
1336
    }
1337
1338
    /**
1339
     * Removes an "action panel" tasks
1340
     *
1341
     * @param  string $panelKey
1342
     * @return $this
1343
     */
1344
    public function removeActionPanelTask($panelKey)
1345
    {
1346
        if (array_key_exists($panelKey, $this->actionTasks)) {
1347
            unset($this->actionTasks[$panelKey]);
1348
        }
1349
        return $this;
1350
    }
1351
}
1352