FileDownloadController   A
last analyzed

Complexity

Total Complexity 12

Size/Duplication

Total Lines 173
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 12
eloc 62
dl 0
loc 173
c 1
b 0
f 1
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A AbsoluteLink() 0 5 1
A sendFile() 0 20 2
B index() 0 57 7
A DownloadLink() 0 8 1
A Link() 0 5 1
1
<?php
2
3
namespace SilverCommerce\DownloadableProducts;
4
5
use DateTime;
6
use SilverStripe\Dev\Debug;
7
use SilverStripe\Assets\File;
8
use SilverStripe\Control\Director;
9
use SilverStripe\Security\Security;
10
use SilverStripe\Control\Controller;
11
use SilverStripe\Core\Config\Config;
12
use SilverStripe\Control\HTTPStreamResponse;
13
use SilverCommerce\OrdersAdmin\Model\Invoice;
14
use SilverStripe\Assets\Flysystem\FlysystemAssetStore;
15
16
/**
17
 * Controller responsible for downloading the resticted file (if the
18
 * user is allowed).
19
 *
20
 * This class will take the file URL, check if the current member (if
21
 * there is one) is allowed to download the file. If not, it will check
22
 * the URL and compare it to the link life of the file (if valid).
23
 */
24
class FileDownloadController extends Controller
25
{
26
27
    /**
28
     * Calculate a timelimit based on the filesize. Set to 0 to give unlimited
29
     * timelimit. The calculation is: give enough time for the user with x kB/s
30
     * connection to donwload the entire file.
31
     *
32
     * E.G. The default 50kB/s equates to 348 minutes per 1GB file.
33
     *
34
     * @var int kilobytes per second
35
     */
36
    private static $min_download_bandwidth = 50;
37
38
    /**
39
     * The base URL segment this controller will be accessed via
40
     * 
41
     * @var string
42
    */
43
    private static $url_segment = "downloadproduct";
44
45
    /**
46
     * {@inheritdoc}
47
     */
48
    private static $url_handlers = [
0 ignored issues
show
introduced by
The private property $url_handlers is not used, and could be removed.
Loading history...
49
        '$ID/$InvoiceID/$AccessKey/$FileName' => 'index'
50
    ];
51
52
    /**
53
     * Generate a link to this controller for downloading a file
54
     * 
55
     * @param int    $id         ID of the file.
56
     * @param int    $invoice_id ID of an associate invoice.
57
     * @param string $access_key Access key of invoice (for security).
58
     * @param string $filename   Actual name of file.
59
     * 
60
     * @return string
61
     */
62
    public function DownloadLink($id, $invoice_id, $access_key, $filename)
63
    {
64
        return Controller::join_links(
65
            $this->AbsoluteLink(),
66
            $id,
67
            $invoice_id,
68
            $access_key,
69
            $filename
70
        );
71
    }
72
73
    /**
74
     * Get a URL to download a file.
75
     *
76
     * @param string $action Action we would like to view.
77
     *
78
     * @return string
79
     */
80
    public function Link($action = NULL)
81
    {
82
        return Controller::join_links(
83
            $this->config()->url_segment,
84
            $action
85
        );
86
    }
87
    
88
    /**
89
     * Get absolute URL to download a file.
90
     *
91
     * @param string $action Action we would like to view.
92
     *
93
     * @return string
94
     */
95
    public function AbsoluteLink($action = NULL)
96
    {
97
        return Controller::join_links(
98
            Director::absoluteBaseURL(),
99
            $this->Link($action)
100
        );
101
    }
102
103
    /**
104
     * Main action for this controller, it handles the security checks and then
105
     * returns the file, or either an error or a login screen.
106
     * 
107
     * @return HTTPResponse
0 ignored issues
show
Bug introduced by
The type SilverCommerce\DownloadableProducts\HTTPResponse was not found. Did you mean HTTPResponse? If so, make sure to prefix the type with \.
Loading history...
108
     */
109
    public function index()
110
    {
111
        $request = $this->getRequest();
112
        $member = Security::getCurrentUser();
113
        $file = File::get()->byID($request->param("ID"));
0 ignored issues
show
Bug introduced by
$request->param('ID') of type string is incompatible with the type integer expected by parameter $id of SilverStripe\ORM\DataList::byID(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

113
        $file = File::get()->byID(/** @scrutinizer ignore-type */ $request->param("ID"));
Loading history...
114
115
        if (empty($file)) {
116
            return $this->httpError(404);
117
        }
118
119
        // Does this file need to have permissions checked?
120
        $product = $file->DownloadableProduct();
121
122
        if (!$product->exists()) {
123
            return $this->redirect($file->AbsoluteLink());
124
        }
125
126
        // If the user is logged in, can they download this file?
127
        if (isset($member) && $product->canDownload($member)) {
128
            $this->extend('onBeforeSendFile', $file);
129
            return $this->sendFile($file);
130
        }
131
        
132
        // Finally  Attempt to get the invoice from the URL vars
133
        // and see if it matches this download
134
        $invoice = Invoice::get()->filter(
135
            [
136
                "ID" => $request->param('InvoiceID'),
137
                "AccessKey" => $request->param('AccessKey')
138
            ]
139
        )->first();
140
    
141
        if (isset($invoice)) {
142
            $origin = new DateTime($invoice->dbObject('StartDate')->Rfc822());
143
            $now = new DateTime();
144
            $diff = (int) $now->diff($origin)->format('%d');
145
146
            if ($diff < $product->LinkLife) {
147
                $this->extend('onBeforeSendFile', $file);
148
                return $this->sendFile($file);
149
            } else {
150
                return Security::permissionFailure(
151
                    $this,
152
                    _t(
153
                        'SilverCommerce\DownloadableProducts.LinkExpired',
154
                        'This download link has now expired.'
155
                    )
156
                );
157
            }
158
        }
159
160
        // Finally, return a login screen
161
        return Security::permissionFailure(
162
            $this,
163
            _t(
164
                'SilverCommerce\DownloadableProducts.NotAuthorised',
165
                'You are not authorised to access this resource. Please log in.'
166
            )
167
        );
168
    }
169
170
    /**
171
     * Output file to the browser as a stream.
172
     * 
173
     * @param File $file A file object
174
     * 
175
     * @return int|boolean
176
     */
177
    protected function sendFile(File $file)
178
    {
179
        $path = $file->getSourceURL(true);
0 ignored issues
show
Unused Code introduced by
The assignment to $path is dead and can be removed.
Loading history...
180
        $size = $file->getAbsoluteSize();
181
        $mime = $file->getMimeType();
182
        $stream = $file->getStream();
183
        $min_bandwidth = $this->config()->min_download_bandwidth;
0 ignored issues
show
Unused Code introduced by
The assignment to $min_bandwidth is dead and can be removed.
Loading history...
184
        $time = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $time is dead and can be removed.
Loading history...
185
186
        // Create streamable response
187
        $response = HTTPStreamResponse::create($stream, $size)
188
            ->addHeader('Content-Type', $mime);
189
190
        // Add standard headers
191
        $headers = Config::inst()->get(FlysystemAssetStore::class, 'file_response_headers');
192
        foreach ($headers as $header => $value) {
193
            $response->addHeader($header, $value);
194
        }
195
196
        return $response;
197
    }
198
}
199