Media::getPath()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace app\models\types;
4
5
use Yii;
6
use Mhor\MediaInfo\MediaInfo;
7
use yii\helpers\FileHelper;
8
use yii\helpers\Url;
9
use yii\web\UploadedFile;
10
use app\models\ContentType;
11
12
/**
13
 * This is the model class for Media content type.
14
 *
15
 * @property \FileInstance $upload
16
 * @property  int $size
17
 */
18
abstract class Media extends ContentType
19
{
20
    const BASE_PATH = 'uploads/';
21
    const TYPE_PATH = 'tmp/';
22
    const BASE_URI = '@web/';
23
    public $usable = false;
24
    public $canPreview = true;
25
26
    public $upload;
27
    public $_size;
28
    public $_filename;
29
30
    /**
31
     * {@inheritdoc}
32
     */
33
    public function upload($fileInstance)
34
    {
35
        if ($fileInstance === null) {
36
            $this->addError('load', Yii::t('app', 'There\'s no file'));
37
38
            return false;
39
        }
40
41
        $this->upload = $fileInstance;
42
43
        $filename = $this->upload->baseName.'.'.$this->upload->extension;
44
        $tmpFilepath = tempnam(sys_get_temp_dir(), 'LCDS_');
45
46
        if ($this->upload->saveAs($tmpFilepath)) {
47 View Code Duplication
            if (static::validateFile($tmpFilepath)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
48
                return ['filename' => $filename, 'tmppath' => $tmpFilepath, 'duration' => static::getDuration($tmpFilepath)];
49
            }
50
            $this->addError('load', Yii::t('app', 'Invalid file'));
51
            unlink($tmpFilepath);
52
53
            return false;
54
        }
55
        $this->addError('load', Yii::t('app', 'Cannot save file'));
56
57
        return false;
58
    }
59
60
    /**
61
     * Custom file validation based on mediainfo description.
62
     *
63
     * @param string $realFilepath filesystem path
64
     *
65
     * @return bool is file valid
66
     */
67
    public static function validateFile($realFilepath)
68
    {
69
        return false;
70
    }
71
72
    /**
73
     * Validate an url based on PHP filter_var.
74
     *
75
     * @param string $url
76
     *
77
     * @return bool valid
78
     */
79
    public static function validateUrl($url)
80
    {
81
        return $url && filter_var($url, FILTER_VALIDATE_URL);
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87
    public function sideload($url)
88
    {
89
        if (!self::validateUrl($url)) {
90
            $this->addError('load', Yii::t('app', 'Empty or incorrect URL'));
91
92
            return false;
93
        }
94
95
        $curl = curl_init($url);
96
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
97
        if (Yii::$app->params['proxy']) {
98
            curl_setopt($curl, CURLOPT_PROXY, Yii::$app->params['proxy']);
99
        }
100
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
101
        curl_setopt($curl, CURLOPT_HEADERFUNCTION, [&$this, 'readHeaderFilename']);
102
        $fileContent = curl_exec($curl);
103
        $error = curl_error($curl);
104
        curl_close($curl);
105
106
        if ($error) {
107
            $this->addError('load', Yii::t('app', $error));
108
109
            return false;
110
        }
111
112
        $filename = $this->_filename;
113
        if (!$filename) {
114
            $urlSplit = explode('/', $url);
115
            $filename = $urlSplit[count($urlSplit) - 1];
116
        }
117
118
        $tmpFilepath = tempnam(sys_get_temp_dir(), 'LCDS_');
119
120
        $file = fopen($tmpFilepath, 'w+');
121
        fwrite($file, $fileContent);
122
        fclose($file);
123
124
        $fileInstance = new UploadedFile();
125
        $fileInstance->name = $filename;
126
        $fileInstance->tempName = $tmpFilepath;
127
        $fileInstance->type = FileHelper::getMimeType($fileInstance->tempName);
128
        $fileInstance->size = $this->_size;
129
        $this->upload = $fileInstance;
130
131 View Code Duplication
        if (static::validateFile($fileInstance->tempName)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
132
            return ['filename' => $filename, 'tmppath' => $tmpFilepath, 'duration' => static::getDuration($tmpFilepath)];
133
        }
134
135
        $this->addError('load', Yii::t('app', 'Invalid file'));
136
        unlink($fileInstance->tempName);
137
138
        return false;
139
    }
140
141
    /**
142
     * Header parsing method used to get size & filename.
143
     *
144
     * @param mixed  $curl   curl handler
145
     * @param string $header
146
     *
147
     * @return int header length
148
     */
149
    public function readHeaderFilename($curl, $header)
150
    {
151
        if (strpos($header, 'Content-Length:') === 0 && preg_match('/(\d+)/', $header, $matches)) {
152
            $this->_size = intval(trim($matches[1]));
153
        } elseif (strpos($header, 'Content-Disposition:') === 0 && preg_match('/filename=(.*)$/', $header, $matches)) {
154
            $this->_filename = trim(str_replace('"', '', $matches[1]));
155
        }
156
157
        return strlen($header);
158
    }
159
160
    /**
161
     * {@inheritdoc}
162
     */
163
    public function getLoadError()
164
    {
165
        $errors = $this->getErrors();
166
        if (array_key_exists('load', $errors) && count($errors['load'])) {
167
            return implode(' - ', $errors['load']);
168
        }
169
170
        return Yii::t('app', 'Incorrect file');
171
    }
172
173
    /**
174
     * Get storage path from web root.
175
     *
176
     * @return string path
177
     */
178
    public static function getPath()
179
    {
180
        return self::BASE_PATH.static::TYPE_PATH;
181
    }
182
183
    /**
184
     * Get Yii aliased storage path.
185
     *
186
     * @return string path
187
     */
188
    public static function getWebPath()
189
    {
190
        return self::BASE_URI.self::getPath();
191
    }
192
193
    /**
194
     * Get filesystem storage path.
195
     *
196
     * @return string path
197
     */
198
    public static function getRealPath()
199
    {
200
        return \Yii::getAlias('@app/').'web/'.self::getPath();
201
    }
202
203
    /**
204
     * Try to get media info for this media.
205
     *
206
     * @return \Mhor\MediaInfo\Container\MediaInfoContainer|null media info
207
     */
208
    protected static function getMediaInfo($realFilepath)
209
    {
210
        try {
211
            return (new MediaInfo())->getInfo($realFilepath);
212
        } catch (\RuntimeException $e) {
213
            return;
214
        }
215
    }
216
217
    /**
218
     * Use mediainfo to parse media duration.
219
     *
220
     * @param string $realFilepath
221
     *
222
     * @return int|null media duration
223
     */
224
    public static function getDuration($realFilepath)
225
    {
226
        $mediainfo = static::getMediaInfo($realFilepath);
227
        if ($mediainfo) {
228
            $general = $mediainfo->getGeneral();
229
            if ($general && $general->get('duration')) {
230
                return ceil($general->get('duration')->getMilliseconds() / 1000);
231
            }
232
        }
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     */
238
    public function transformDataBeforeSave($insert, $data)
239
    {
240
        if ($insert) {
241
            list($path, $filename) = explode('§', $data);
242
            $tmppath = FileHelper::normalizePath($path);
243
244
            $parts = explode(DIRECTORY_SEPARATOR, $tmppath);
245
            array_pop($parts); // Remove filename
246
            if (implode(DIRECTORY_SEPARATOR, $parts) == sys_get_temp_dir() && strpos(DIRECTORY_SEPARATOR, $filename) === false && file_exists($tmppath)) {
247
                ini_set('mbstring.substitute_character', 'none');
248
                $filename = mb_convert_encoding($filename, 'ASCII');
249
                $filename = static::getUniqFilename(static::getRealPath(), $filename);
250
                $data = static::getWebPath().$filename;
251
                $realFilepath = static::getRealPath().$filename;
252
253
                if (rename($tmppath, $realFilepath)) {
254
                    return $data;
255
                }
256
            }
257
258
            return null;
259
        }
260
261
        return $data;
262
    }
263
264
    /**
265
     * Create unique filename by checking for existence and appending to filename.
266
     *
267
     * @param string $path     filepath
268
     * @param string $filename
269
     *
270
     * @return string unique filename
271
     */
272
    protected static function getUniqFilename($path, $filename)
273
    {
274
        if (!file_exists($path.$filename)) {
275
            return $filename;
276
        }
277
278
        $parts = explode('.', $filename);
279
        if (count($parts) > 1) {
280
            $ext = array_pop($parts);
281
        } else {
282
            $ext = null;
283
        }
284
        $name = implode('.', $parts);
285
286
        $i = 1;
287
        $filename = $name.$i.($ext ? '.'.$ext : '');
288
        while (file_exists($path.$filename)) {
289
            $filename = $name.++$i.($ext ? '.'.$ext : '');
290
        }
291
292
        return $filename;
293
    }
294
295
    /**
296
     * {@inheritdoc}
297
     */
298
    public function processData($data)
299
    {
300
        return Url::to($data);
301
    }
302
}
303