Completed
Push — master ( f9f56b...97d556 )
by Robbie
03:43 queued 02:06
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));
138
        }
139
140
        return $this->canEdit($member);
141
    }
142
143
    public function canEdit($member = null)
144
    {
145 View Code Duplication
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
146
            $member = Member::currentUser();
147
        }
148
149
        $results = $this->extend('canEdit', $member);
150
151
        if ($results && is_array($results)) {
152
            if (!min($results)) {
153
                return false;
154
            }
155
        }
156
157
        // Do early admin check
158
        if ($member && Permission::checkMember($member, array('ADMIN','SITETREE_EDIT_ALL'))) {
159
            return true;
160
        }
161
162
        if ($this->CanEditType === 'LoggedInUsers') {
163
            return $member && $member->exists();
164
        }
165
166
        if ($this->CanEditType === 'OnlyTheseUsers' && $this->EditorGroups()->count()) {
167
            return $member && $member->inGroups($this->EditorGroups());
168
        }
169
170
        return false;
171
    }
172
173
    /**
174
     * @param Member $member
175
     *
176
     * @return boolean
177
     */
178
    public function canCreate($member = null)
179
    {
180 View Code Duplication
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
181
            $member = Member::currentUser();
182
        }
183
184
        $results = $this->extend('canCreate', $member);
185
186
        if ($results && is_array($results)) {
187
            if (!min($results)) {
188
                return false;
189
            }
190
        }
191
192
        // Do early admin check
193
        if ($member &&
194
            Permission::checkMember($member, array('CMS_ACCESS_DMSDocumentAdmin'))
195
        ) {
196
            return true;
197
        }
198
199
        return $this->canEdit($member);
200
    }
201
202
    /**
203
     * @param Member $member
204
     *
205
     * @return boolean
206
     */
207
    public function canDelete($member = null)
208
    {
209 View Code Duplication
        if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
210
            $member = Member::currentUser();
211
        }
212
213
        $results = $this->extend('canDelete', $member);
214
215
        if ($results && is_array($results)) {
216
            if (!min($results)) {
217
                return false;
218
            }
219
        }
220
221
        return $this->canEdit($member);
222
    }
223
224
    /**
225
     * Increase ViewCount by 1, without update any other record fields such as
226
     * LastEdited.
227
     *
228
     * @return DMSDocument
229
     */
230
    public function trackView()
231
    {
232
        if ($this->ID > 0) {
233
            $count = $this->ViewCount + 1;
234
235
            $this->ViewCount = $count;
236
237
            DB::query("UPDATE \"DMSDocument\" SET \"ViewCount\"='$count' WHERE \"ID\"={$this->ID}");
238
        }
239
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)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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