1
|
|
|
<?php |
|
|
|
|
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* Created by PhpStorm. |
5
|
|
|
* User: steve |
6
|
|
|
* Date: 4/17/2018 |
7
|
|
|
* Time: 10:09 PM |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
if ( !class_exists("FooGallery_Pro_Video_YouTube") ){ |
11
|
|
|
|
12
|
|
|
require_once dirname(__FILE__) . '/class-foogallery-pro-video-base.php'; |
13
|
|
|
|
14
|
|
|
class FooGallery_Pro_Video_YouTube extends FooGallery_Pro_Video_Base { |
|
|
|
|
15
|
|
|
|
16
|
|
|
// region Properties |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* The regular expression used to match a YouTube video URL. |
20
|
|
|
* @var string |
21
|
|
|
*/ |
22
|
|
|
public $regex_pattern; |
23
|
|
|
|
24
|
|
|
// endregion |
25
|
|
|
|
26
|
|
|
function __construct() { |
|
|
|
|
27
|
|
|
$this->regex_pattern = '/(www\.)?youtube|youtu\.be/i'; |
28
|
|
|
} |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Takes a URL and checks if this class handles it. |
32
|
|
|
* |
33
|
|
|
* @param string $url The URL to check. |
34
|
|
|
* @param array &$matches Optional. If matches is provided, it is passed to the `preg_match` call used to check the URL. |
35
|
|
|
* @return int Returns 1 if the URL is handled, 0 if it is not, or FALSE if an error occurred. |
36
|
|
|
*/ |
37
|
|
|
function handles($url, &$matches = array()){ |
|
|
|
|
38
|
|
|
return preg_match($this->regex_pattern, $url, $matches); |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Takes a string value and determines whether or not it is a YouTube video id. |
43
|
|
|
* |
44
|
|
|
* @param string $value The value to test. |
45
|
|
|
* @return bool |
46
|
|
|
* |
47
|
|
|
* @see https://stackoverflow.com/questions/6180138/whats-the-maximum-length-of-a-youtube-video-id |
48
|
|
|
* |
49
|
|
|
* @description At present YouTube's video ids always have 11 characters, while this is not |
50
|
|
|
* guaranteed it should stay that way until all 73,786,976,294,838,206,464 possible combinations |
51
|
|
|
* are exhausted. |
52
|
|
|
*/ |
53
|
|
|
function is_video_id($value) { |
|
|
|
|
54
|
|
|
return !empty($value) && is_string($value) && strlen($value) === 11 && strpos($value, " ") === false && $this->url_exists("https://www.youtube.com/watch?v=" . $value); |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Takes a string value and determines whether or not it is a YouTube playlist id. |
59
|
|
|
* |
60
|
|
|
* @param string $value The value to test. |
61
|
|
|
* @return bool |
62
|
|
|
* |
63
|
|
|
* @description At present YouTube's playlist ids always have 34 characters and begin with "PL", while |
64
|
|
|
* this is not guaranteed it should stay that way until all possible combinations are exhausted. |
65
|
|
|
*/ |
66
|
|
|
function is_playlist_id($value) { |
|
|
|
|
67
|
|
|
return !empty($value) && is_string($value) && strlen($value) === 34 && strpos($value, " ") === false && $this->url_exists("https://www.youtube.com/playlist?list=" . $value); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Takes the supplied query and optional page number, determines the correct method to call and then returns its' data. |
72
|
|
|
* |
73
|
|
|
* @param string $query The query value to parse. |
74
|
|
|
* @param int [$page=1] If this is a search query the page number could also be supplied. |
75
|
|
|
* @param int [$offset=0] The number of items already retrieved for the query. |
76
|
|
|
* @return array |
77
|
|
|
*/ |
78
|
|
|
function query($query, $page = 1, $offset = 0) { |
|
|
|
|
79
|
|
|
if ($this->is_playlist_id($query)) { |
80
|
|
|
return $this->fetch_playlist($query); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
if ($this->is_video_id($query)) { |
84
|
|
|
return $this->fetch_video($query); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
// if we get here we assume the query is a search |
88
|
|
|
return $this->search($query, $page, $offset); |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* Takes the supplied YouTube url and attempts to fetch its' data. |
93
|
|
|
* |
94
|
|
|
* @description At present this method supports the following url patterns: |
95
|
|
|
* |
96
|
|
|
* - http(s)://www.youtube.com/watch?v=[ID] |
97
|
|
|
* - http(s)://youtu.be/[ID] |
98
|
|
|
* - http(s)://www.youtube.com/embed/[ID] |
99
|
|
|
* - http(s)://www.youtube.com/playlist?list=[ID] |
100
|
|
|
* |
101
|
|
|
* @param string $url The url to fetch the data for. |
102
|
|
|
* @return array |
103
|
|
|
*/ |
104
|
|
|
function fetch($url) { |
|
|
|
|
105
|
|
|
// make sure we're dealing with a YouTube url in case this method is called externally |
106
|
|
|
if (preg_match($this->regex_pattern, $url)) { |
107
|
|
|
$query_string = array(); |
108
|
|
|
$url_parts = parse_url($url); |
109
|
|
|
// check if we were supplied a query string i.e. anything after ? |
110
|
|
|
if (!empty($url_parts["query"])) { |
111
|
|
|
// if we have a query string then parse it into an array of key value pairs |
112
|
|
|
parse_str($url_parts["query"], $query_string); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
// check if we are dealing with a playlist url |
116
|
|
|
if (preg_match('/(www\.)?youtube\.com\/playlist/i', $url)) { |
117
|
|
|
return $this->fetch_playlist($query_string["list"]); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
// otherwise we are dealing with one of the single video supported formats |
121
|
|
|
$id = $query_string["v"]; |
122
|
|
|
// if the id does not exist in the query string then we are dealing with a YouTube |
123
|
|
|
// short or embed url so grab the id from the last part of the url |
124
|
|
|
if (empty($id) && preg_match('/(www\.)?youtube\.com\/embed|youtu\.be/i', $url)) { |
125
|
|
|
// here we split the url on all forward-slashes |
126
|
|
|
$parts = explode("/", $url); |
127
|
|
|
// then grab the last part to use as the id |
128
|
|
|
$id = end($parts); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
return $this->fetch_video($id); |
132
|
|
|
} |
133
|
|
|
return $this->error_response("Unrecognized YouTube url."); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* Takes the supplied YouTube playlist id and fetches its' data. |
138
|
|
|
* |
139
|
|
|
* @param string $id The playlist id to fetch. |
140
|
|
|
* @return array( |
|
|
|
|
141
|
|
|
* "mode" => "playlist", |
142
|
|
|
* "thumbnail" => string, |
143
|
|
|
* "title" => string, |
144
|
|
|
* "description" => string, |
145
|
|
|
* "videos" => array, |
146
|
|
|
* "total" => number |
147
|
|
|
* ) |
148
|
|
|
* @return array( |
|
|
|
|
149
|
|
|
* "mode" => "error", |
150
|
|
|
* "message" => string |
151
|
|
|
* ) |
152
|
|
|
*/ |
153
|
|
|
function fetch_playlist($id) { |
|
|
|
|
154
|
|
|
|
155
|
|
|
if (!$this->is_playlist_id($id)) { |
156
|
|
|
return $this->error_response("Invalid playlist id supplied."); |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
// we have as valid an id as we can hope for until we make the actual request so request it |
160
|
|
|
$url = "https://www.youtube.com/list_ajax?style=json&action_get_list=true&list=" . $id; |
161
|
|
|
// get the json object from the supplied url |
162
|
|
|
$json = $this->json_get($url); |
163
|
|
|
|
164
|
|
|
// if an error occurred return it |
165
|
|
|
if ($this->is_error($json)) { |
166
|
|
|
return $json; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
// do basic validation on the json object |
170
|
|
|
if (empty($json) || empty($json->video) || !is_array($json->video)) { |
171
|
|
|
return $this->error_response("No videos in response."); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
// if we get here we need to build a response to return to the frontend |
175
|
|
|
$response = array( |
176
|
|
|
"mode" => "playlist", |
177
|
|
|
"id" => $id, |
178
|
|
|
"total" => 0, |
179
|
|
|
"page" => 1, |
180
|
|
|
"nextPage" => 0, |
181
|
|
|
"offset" => 0, |
182
|
|
|
"videos" => array() |
183
|
|
|
); |
184
|
|
|
|
185
|
|
|
// iterate each of the returned videos and add them to the response in our desired format |
186
|
|
|
foreach ($json->video as $video) { |
187
|
|
|
if (!empty($video->thumbnail) && !empty($video->encrypted_id) && !empty($video->title)) { |
188
|
|
|
$response["videos"][] = array( |
189
|
|
|
"provider" => "youtube", |
190
|
|
|
"id" => $video->encrypted_id, |
191
|
|
|
"url" => "https://www.youtube.com/embed/" . $video->encrypted_id, |
192
|
|
|
"thumbnail" => $video->thumbnail, |
193
|
|
|
"title" => $video->title, |
194
|
|
|
"description" => !empty($video->description) ? $video->description : "" |
195
|
|
|
); |
196
|
|
|
} |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
// update the offset and total with the current video count |
200
|
|
|
$response["offset"] = $response["total"] = count($response["videos"]); |
201
|
|
|
|
202
|
|
|
// if we have no videos then none were valid |
203
|
|
|
if ($response["total"] === 0) { |
204
|
|
|
return $this->error_response("No valid videos in response."); |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
return $response; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* Takes the supplied YouTube video id and fetches its' data. |
212
|
|
|
* |
213
|
|
|
* @param string $id The video id to fetch. |
214
|
|
|
* @return array( |
|
|
|
|
215
|
|
|
* "mode" => "video", |
216
|
|
|
* "url" => string, |
217
|
|
|
* "thumbnail" => string, |
218
|
|
|
* "title" => string, |
219
|
|
|
* "description" => string |
220
|
|
|
* ) |
221
|
|
|
* @return array( |
|
|
|
|
222
|
|
|
* "mode" => "error", |
223
|
|
|
* "message" => string |
224
|
|
|
* ) |
225
|
|
|
*/ |
226
|
|
|
function fetch_video($id) { |
|
|
|
|
227
|
|
|
|
228
|
|
|
if (!$this->is_video_id($id)) { |
229
|
|
|
return $this->error_response("Invalid video id supplied."); |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
// we have as valid an id as we can hope for until we make the actual request so request it |
233
|
|
|
$url = "http://www.youtube.com/oembed?url=" . urlencode("https://www.youtube.com/watch?v=" . $id); |
234
|
|
|
// get the json object from the supplied url |
235
|
|
|
$json = $this->json_get($url); |
236
|
|
|
|
237
|
|
|
// if an error occurred return it |
238
|
|
|
if ($this->is_error($json)) { |
239
|
|
|
return $json; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
// do basic validation on the parsed object |
243
|
|
|
if (empty($json) || empty($json->thumbnail_url) || empty($json->title)) { |
244
|
|
|
return $this->error_response("No video in response."); |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
$response = array( |
248
|
|
|
"mode" => "single", |
249
|
|
|
"videos" => array() |
250
|
|
|
); |
251
|
|
|
|
252
|
|
|
$response["videos"][] = array( |
253
|
|
|
"provider" => "youtube", |
254
|
|
|
"id" => $id, |
255
|
|
|
"url" => "https://www.youtube.com/embed/" . $id, |
256
|
|
|
"thumbnail" => $json->thumbnail_url, |
257
|
|
|
"title" => $json->title, |
258
|
|
|
"description" => !empty($json->description) ? $json->description : "" |
259
|
|
|
); |
260
|
|
|
|
261
|
|
|
return $response; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* Takes the supplied query and optional page number and performs a YouTube search. |
266
|
|
|
* |
267
|
|
|
* @param string $query The query to use as a search term. |
268
|
|
|
* @param int [$page=1] The page number to retrieve. |
269
|
|
|
* @param int [$offset=0] The number of items already retrieved for the query. |
270
|
|
|
* @return array( |
|
|
|
|
271
|
|
|
* "mode" => "search", |
272
|
|
|
* "total" => number, |
273
|
|
|
* "page" => number, |
274
|
|
|
* "offset" => number, |
275
|
|
|
* "nextPage" => number, |
276
|
|
|
* "videos" => array( |
277
|
|
|
* array( |
278
|
|
|
* "provider" => "youtube", |
279
|
|
|
* "id" => string, |
280
|
|
|
* "url" => string, |
281
|
|
|
* "thumbnail" => string, |
282
|
|
|
* "title" => string, |
283
|
|
|
* "description" => string |
284
|
|
|
* ) |
285
|
|
|
* ) |
286
|
|
|
* ) |
287
|
|
|
* @return array( |
|
|
|
|
288
|
|
|
* "mode" => "error", |
289
|
|
|
* "title" => string, |
290
|
|
|
* "message" => string |
291
|
|
|
* ) |
292
|
|
|
*/ |
293
|
|
|
function search($query, $page = 1, $offset = 0) { |
|
|
|
|
294
|
|
|
if (empty($query)) { |
295
|
|
|
return $this->error_response("Empty search query."); |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
if ($query === "!ERROR"){ |
299
|
|
|
return $this->error_response("Dummy error."); |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
$url = "https://www.youtube.com/search_ajax?style=json&search_query=" . urlencode($query) . "&page=" . $page; |
303
|
|
|
// get the json object from the supplied url |
304
|
|
|
$json = $this->json_get($url); |
305
|
|
|
|
306
|
|
|
// if an error occurred return it |
307
|
|
|
if ($this->is_error($json)) { |
308
|
|
|
return $json; |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
// do basic validation on the parsed object |
312
|
|
|
if (empty($json) || empty($json->hits) || empty($json->video) || !is_array($json->video)) { |
313
|
|
|
return $this->error_response("No videos in response."); |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
// if we get here we need to build a response to return to the frontend |
317
|
|
|
$response = array( |
318
|
|
|
"mode" => "search", |
319
|
|
|
"total" => $json->hits, |
320
|
|
|
"page" => $page, |
321
|
|
|
"offset" => $offset, |
322
|
|
|
"nextPage" => 0, |
323
|
|
|
"videos" => array() |
324
|
|
|
); |
325
|
|
|
|
326
|
|
|
// iterate each of the returned videos and add them to the response in our desired format |
327
|
|
|
foreach ($json->video as $video) { |
328
|
|
|
if (!empty($video->thumbnail) && !empty($video->encrypted_id) && !empty($video->title)) { |
329
|
|
|
$response["videos"][] = array( |
330
|
|
|
"provider" => "youtube", |
331
|
|
|
"id" => $video->encrypted_id, |
332
|
|
|
"url" => "https://www.youtube.com/embed/" . $video->encrypted_id, |
333
|
|
|
"thumbnail" => $video->thumbnail, |
334
|
|
|
"title" => $video->title, |
335
|
|
|
"description" => !empty($video->description) ? $video->description : "" |
336
|
|
|
); |
337
|
|
|
} |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
// if we have no videos then none of the videos were valid |
341
|
|
|
if ($response["total"] === 0) { |
342
|
|
|
return $this->error_response("No valid videos in response."); |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
$response["offset"] = $response["offset"] + count($response["videos"]); |
346
|
|
|
if ($response["total"] > $response["offset"]){ |
347
|
|
|
$response["nextPage"] = $response["page"] + 1; |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
return $response; |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
} |
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.