Completed
Push — master ( 31b3ff...06692e )
by Robbie
03:48 queued 01:52
created

code/model/DMSDocument.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * @package dms
5
 *
6
 * @property Varchar Filename
7
 * @property Varchar Folder
8
 * @property Varchar Title
9
 * @property Text Description
10
 * @property int ViewCount
11
 * @property Boolean EmbargoedIndefinitely
12
 * @property Boolean EmbargoedUntilPublished
13
 * @property DateTime EmbargoedUntilDate
14
 * @property DateTime ExpireAtDate
15
 * @property Enum DownloadBehavior
16
 * @property Enum CanViewType Enum('Anyone, LoggedInUsers, OnlyTheseUsers', 'Anyone')
17
 * @property Enum CanEditType Enum('LoggedInUsers, OnlyTheseUsers', 'LoggedInUsers')
18
 *
19
 * @method ManyManyList RelatedDocuments
20
 * @method ManyManyList ViewerGroups
21
 * @method ManyManyList EditorGroups
22
 *
23
 * @method Member CreatedBy
24
 * @property Int CreatedByID
25
 * @method Member LastEditedBy
26
 * @property Int LastEditedByID
27
 *
28
 */
29
class DMSDocument extends DataObject implements DMSDocumentInterface
30
{
31
    private static $db = array(
32
        "Filename" => "Varchar(255)", // eg. 3469~2011-energysaving-report.pdf
33
        "Folder" => "Varchar(255)",    // eg.	0
34
        "Title" => 'Varchar(1024)', // eg. "Energy Saving Report for Year 2011, New Zealand LandCorp"
35
        "Description" => 'Text',
36
        "ViewCount" => 'Int',
37
        "EmbargoedIndefinitely" => 'Boolean(false)',
38
        "EmbargoedUntilPublished" => 'Boolean(false)',
39
        "EmbargoedUntilDate" => 'SS_DateTime',
40
        "ExpireAtDate" => 'SS_DateTime',
41
        "DownloadBehavior" => 'Enum(array("open","download"), "download")',
42
        "CanViewType" => "Enum('Anyone, LoggedInUsers, OnlyTheseUsers', 'Anyone')",
43
        "CanEditType" => "Enum('LoggedInUsers, OnlyTheseUsers', 'LoggedInUsers')",
44
    );
45
46
    private static $belongs_many_many = array(
47
        'Sets' => 'DMSDocumentSet'
48
    );
49
50
    private static $has_one = array(
51
        'CoverImage' => 'Image',
52
        'CreatedBy' => 'Member',
53
        'LastEditedBy' => 'Member',
54
    );
55
56
    private static $many_many = array(
57
        'RelatedDocuments' => 'DMSDocument',
58
        'ViewerGroups' => 'Group',
59
        'EditorGroups' => 'Group',
60
    );
61
62
    private static $display_fields = array(
63
        'ID' => 'ID',
64
        'Title' => 'Title',
65
        'FilenameWithoutID' => 'Filename',
66
        'LastEdited' => 'Last Edited'
67
    );
68
69
    private static $singular_name = 'Document';
70
71
    private static $plural_name = 'Documents';
72
73
    private static $summary_fields = array(
74
        'Filename' => 'Filename',
75
        'Title' => 'Title',
76
        'getRelatedPages.count' => 'Page Use',
77
        'ViewCount' => 'ViewCount',
78
    );
79
80
    /**
81
     * @var string download|open
82
     * @config
83
     */
84
    private static $default_download_behaviour = 'download';
85
86
    /**
87
     * A key value map of the "actions" tabs that will be added to the CMS fields
88
     *
89
     * @var array
90
     */
91
    protected $actionTasks = array(
92
        'embargo' => 'Embargo',
93
        'expiry' => 'Expiry',
94
        'replace' => 'Replace',
95
        'find-usage' => 'Usage',
96
        'find-references' => 'References',
97
        'find-relateddocuments' => 'Related Documents',
98
        'permissions' => 'Permissions'
99
    );
100
101
    public function canView($member = null)
102
    {
103 View Code Duplication
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
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));
0 ignored issues
show
Documentation Bug introduced by
The method ViewerGroups does not exist on object<DMSDocument>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
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
0 ignored issues
show
Should the type for parameter $member not be Member|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

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