Issues (47)

src/helpers/VideosHelper.php (5 issues)

1
<?php
2
/**
3
 * @link      https://dukt.net/videos/
4
 * @copyright Copyright (c) Dukt
5
 * @license   https://github.com/dukt/videos/blob/v2/LICENSE.md
6
 */
7
8
namespace dukt\videos\helpers;
9
10
use Craft;
11
use craft\helpers\FileHelper;
12
use dukt\videos\errors\ApiResponseException;
13
use dukt\videos\models\Video;
14
use dukt\videos\Plugin;
15
16
/**
17
 * Videos helper
18
 */
19
class VideosHelper
20
{
21
    // Public Methods
22
    // =========================================================================
23
24
    /**
25
     * Formats seconds to hh:mm:ss.
26
     *
27
     * @param $seconds
28
     *
29
     * @return string
30
     */
31
    public static function getDuration($seconds): string
32
    {
33
        $hours = (int)((int)$seconds / 3600);
34
        $minutes = (($seconds / 60) % 60);
35
        $seconds %= 60;
36
37
        $hms = '';
38
39
        if ($hours > 0) {
40
            $hms .= str_pad($hours, 2, '0', STR_PAD_LEFT) . ':';
41
        }
42
43
        $hms .= str_pad($minutes, 2, '0', STR_PAD_LEFT) . ':';
44
45
        $hms .= str_pad($seconds, 2, '0', STR_PAD_LEFT);
46
47
        return $hms;
48
    }
49
50
    /**
51
     * Formats seconds to ISO 8601 duration
52
     *
53
     * @param $seconds
54
     *
55
     * @return string
56
     */
57
    public static function getDuration8601($seconds): string
58
    {
59
        $hours = (int)((int)$seconds / 3600);
60
        $minutes = (($seconds / 60) % 60);
61
        $seconds %= 60;
62
63
        $iso8601 = 'PT';
64
65
        if ($hours > 0) {
66
            $iso8601 .= sprintf('%dH', $hours);
67
        }
68
69
        if ($minutes > 0) {
70
            $iso8601 .= sprintf('%dM', $minutes);
71
        }
72
73
        return $iso8601 . sprintf('%dS', $seconds);
74
    }
75
76
    /**
77
     * Returns a video thumbnail’s published URL.
78
     *
79
     * @param $gatewayHandle
80
     * @param $videoId
81
     * @param $size
82
     *
83
     * @return null|string
84
     * @throws \GuzzleHttp\Exception\GuzzleException
85
     * @throws \craft\errors\ImageException
86
     * @throws \yii\base\Exception
87
     * @throws \yii\base\InvalidConfigException
88
     */
89
    public static function getVideoThumbnail($gatewayHandle, $videoId, $size): ?string
90
    {
91
        $baseDir = Craft::$app->getPath()->getRuntimePath() . DIRECTORY_SEPARATOR . 'videos' . DIRECTORY_SEPARATOR . 'thumbnails' . DIRECTORY_SEPARATOR . $gatewayHandle . DIRECTORY_SEPARATOR . $videoId;
92
        $originalDir = $baseDir . DIRECTORY_SEPARATOR . 'original';
93
        $dir = $baseDir . DIRECTORY_SEPARATOR . $size;
94
95
        $file = self::getThumbnailFile($dir);
96
97
        if (!$file) {
98
            // Retrieve original image
99
            $originalPath = null;
100
101
            if (is_dir($originalDir)) {
102
                $originalFiles = FileHelper::findFiles($originalDir);
103
104
                if ($originalFiles !== []) {
105
                    $originalPath = $originalFiles[0];
106
                }
107
            }
108
109
            if (!$originalPath) {
110
                try {
111
                    $video = Plugin::$plugin->getVideos()->getVideoById($gatewayHandle, $videoId);
112
                } catch (ApiResponseException $apiResponseException) {
113
                    Craft::info('Couldn’t get video thumbnail:' . "\r\n"
114
                        . 'Message: ' . "\r\n" . $apiResponseException->getMessage() . "\r\n"
115
                        . 'Trace: ' . "\r\n" . $apiResponseException->getTraceAsString(), __METHOD__);
116
                    return null;
117
                }
118
119
                $url = $video->thumbnailSource;
120
121
                $name = pathinfo($url, PATHINFO_BASENAME);
122
                $originalPath = $originalDir . DIRECTORY_SEPARATOR . $name;
0 ignored issues
show
Are you sure $name of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

122
                $originalPath = $originalDir . DIRECTORY_SEPARATOR . /** @scrutinizer ignore-type */ $name;
Loading history...
123
124
                FileHelper::createDirectory($originalDir);
125
                $client = new \GuzzleHttp\Client();
126
                $response = $client->request('GET', $url, [
127
                    'sink' => $originalPath,
128
                ]);
129
130
                // Make sure the original file has an extension
131
                $mimeByExt = FileHelper::getMimeTypeByExtension($originalPath);
132
133
                if (!$mimeByExt) {
134
                    // Add the extension to the filename if it doesn’t have one
135
                    $mime = FileHelper::getMimeType($originalPath);
136
                    $ext = FileHelper::getExtensionByMimeType($mime);
137
138
                    if ($ext !== '' && $ext !== '0') {
139
                        $name .= '.' . $ext;
140
                        $targetPath = $originalDir . DIRECTORY_SEPARATOR . $name;
141
142
                        rename($originalPath, $targetPath);
143
144
                        $originalPath = $targetPath;
145
                    }
146
                }
147
148
                if ($response->getStatusCode() !== 200) {
149
                    return null;
150
                }
151
            } else {
152
                $name = pathinfo($originalPath, PATHINFO_BASENAME);
153
            }
154
155
            // Generate the thumb
156
            $path = $dir . DIRECTORY_SEPARATOR . $name;
157
            FileHelper::createDirectory($dir);
158
            Craft::$app->getImages()->loadImage($originalPath, false, $size)
159
                ->scaleToFit($size, $size)
160
                ->saveAs(parse_url($path, PHP_URL_PATH));
161
        } else {
162
            $name = pathinfo($file, PATHINFO_BASENAME);
163
        }
164
165
        return Craft::$app->getAssetManager()->getPublishedUrl($dir, true) . sprintf('/%s', $name);
0 ignored issues
show
The call to yii\web\AssetManager::getPublishedUrl() has too many arguments starting with true. ( Ignorable by Annotation )

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

165
        return Craft::$app->getAssetManager()->/** @scrutinizer ignore-call */ getPublishedUrl($dir, true) . sprintf('/%s', $name);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Are you sure Craft::app->getAssetMana...ublishedUrl($dir, true) of type false|mixed|string can be used in concatenation? ( Ignorable by Annotation )

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

165
        return /** @scrutinizer ignore-type */ Craft::$app->getAssetManager()->getPublishedUrl($dir, true) . sprintf('/%s', $name);
Loading history...
It seems like $name can also be of type array; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

165
        return Craft::$app->getAssetManager()->getPublishedUrl($dir, true) . sprintf('/%s', /** @scrutinizer ignore-type */ $name);
Loading history...
166
    }
167
168
    /**
169
     * Transforms a video model into an array.
170
     *
171
     * @param Video $videoModel
172
     * @return array
173
     * @throws \GuzzleHttp\Exception\GuzzleException
174
     * @throws \craft\errors\ImageException
175
     * @throws \yii\base\Exception
176
     * @throws \yii\base\InvalidConfigException
177
     */
178
    public static function videoToArray(Video $videoModel): array
179
    {
180
        $video = $videoModel->toArray([
181
            'id',
182
            'gatewayHandle',
183
            'title',
184
            'url',
185
            'authorName',
186
            'authorUrl',
187
            'durationSeconds',
188
            'plays',
189
            'private'
190
        ]);
191
192
        if ($videoModel->id && $videoModel->gatewayHandle) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $videoModel->id of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
193
            $video['thumbnail'] = $videoModel->thumbnailSource;
194
            $video['embedUrl'] = $videoModel->getEmbedUrl();
195
            $video['duration'] = $videoModel->getDuration();
196
        }
197
198
        $video['errors'] = $videoModel->getErrors();
199
        $video['hasErrors'] = $videoModel->hasErrors();
200
201
        return $video;
202
    }
203
204
    // Private Methods
205
    // =========================================================================
206
207
    /**
208
     * Get thumbnail file.
209
     *
210
     * @param $dir
211
     *
212
     * @return null|string
213
     */
214
    private static function getThumbnailFile($dir)
215
    {
216
        if (!is_dir($dir)) {
217
            return null;
218
        }
219
220
        $files = FileHelper::findFiles($dir);
221
222
        if ($files === []) {
223
            return null;
224
        }
225
226
        return $files[0];
227
    }
228
}
229