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

code/model/DMSDocument.php (1 issue)

Upgrade to new PHP Analysis Engine

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

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