PluginProbe
FV Player 8 / trunk
FV Player 8 vtrunk
trunk 8.0.18 8.0.19 8.0.20 8.0.21 8.0.25 8.0.27 8.1 8.1.3
fv-player / models / player-position-save.php

player-position-save.php in FV Player 8 trunk, at models/player-position-save.php

603 lines 17.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit;
5 }
6
7 class FV_Player_Position_Save {
8
9 /**
10 * Stores cache of user video positions
11 *
12 * @var array
13 */
14 private static $cache = array();
15
16 public function __construct() {
17 add_action( 'fv_player_update', array( $this, 'plugin_update_database' ), 9 );
18 add_action( 'wp_ajax_fv_wp_flowplayer_video_position_save', array($this, 'video_position_save' ) );
19 add_filter( 'fv_player_item', array( $this, 'set_last_positions' ), 10, 3 );
20 add_filter( 'fv_flowplayer_admin_default_options_after', array( $this, 'player_position_save_admin_default_options_html' ) );
21 add_filter( 'fv_flowplayer_attributes', array( $this, 'shortcode' ), 10, 3 );
22 }
23
24 function plugin_update_database() {
25 global $wpdb;
26
27 // create table to store user video positions
28 $sql_user_video_positions = "CREATE TABLE ".$wpdb->prefix."fv_player_user_video_positions (
29 id int(11) NOT NULL auto_increment,
30 user_id int(11) NOT NULL,
31 video_id int(11) NOT NULL,
32 last_position int(11) NOT NULL,
33 top_position int(11) NOT NULL,
34 finished tinyint(1) NOT NULL,
35 legacy_video_id varchar(255) NOT NULL,
36 ab_start int(11) NOT NULL,
37 ab_end int(11) NOT NULL,
38 last_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
39 PRIMARY KEY (id),
40 KEY user_id (user_id),
41 KEY video_id (video_id),
42 Key legacy_video_id (legacy_video_id)
43 )" . $wpdb->get_charset_collate() . ";";
44
45 // create table to store position in playlists
46 $sql_playlist_positions = "CREATE TABLE ".$wpdb->prefix."fv_player_user_playlist_positions (
47 id int(11) NOT NULL auto_increment,
48 user_id int(11) NOT NULL,
49 player_id int(11) NOT NULL,
50 item_index int(11) NOT NULL,
51 last_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
52 PRIMARY KEY (id),
53 KEY user_id (user_id),
54 KEY player_id (player_id)
55 )" . $wpdb->get_charset_collate() . ";";
56
57 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
58
59 dbDelta( $sql_user_video_positions );
60 dbDelta( $sql_playlist_positions );
61 }
62
63 /**
64 * Set cache to static variable
65 *
66 * @param int $user_id
67 * @param string $type video, playlist
68 *
69 * @return array
70 */
71 private function set_cache($user_id, $type) {
72 global $wpdb;
73
74 if( !isset(self::$cache[$user_id]) ) {
75 self::$cache[$user_id] = array();
76 }
77
78 if( $type == 'video') {
79 self::$cache[$user_id][$type] = $wpdb->get_results( $wpdb->prepare(
80 "SELECT video_id, last_position, top_position, finished, legacy_video_id, ab_start, ab_end FROM `{$wpdb->prefix}fv_player_user_video_positions` WHERE user_id = %d",
81 $user_id
82 ) );
83 }
84
85 if( $type == 'playlist' ) {
86 self::$cache[$user_id][$type] = $wpdb->get_results( $wpdb->prepare(
87 "SELECT player_id, item_index FROM `{$wpdb->prefix}fv_player_user_playlist_positions` WHERE user_id = %d",
88 $user_id
89 ) );
90 }
91
92 return self::$cache[$user_id][$type];
93 }
94
95 /**
96 * Get cache from static variable
97 *
98 * @param int $user_id
99 * @param string $type video, playlist
100 *
101 * @return array
102 */
103 private function get_cache($user_id, $type) {
104 if ( ! isset( self::$cache[$user_id][$type] ) ) {
105 $this->set_cache($user_id, $type);
106 }
107
108 return self::$cache[$user_id][$type];
109 }
110
111 /**
112 * Get video position
113 *
114 * @param int $user_id
115 * @param int|string $video_id
116 * @param string $type last_position, top_position, finished
117 *
118 * @return int
119 */
120 function get_video_position( $user_id, $video_id, $type ) {
121 $cache = self::get_cache($user_id, 'video');
122 $value = 0;
123
124 if( is_numeric($video_id) ) { // id
125 $video_id = intval($video_id);
126
127 if(is_array($cache)) {
128 foreach ($cache as $cache_item) {
129 if ($cache_item->video_id == $video_id) {
130 $value = $cache_item->$type;
131 break;
132 }
133 }
134 }
135 } else { // legacy_video_id
136 if( is_array($cache)) {
137 foreach ($cache as $cache_item) {
138 if ($cache_item->legacy_video_id == $video_id) {
139 $value = $cache_item->$type;
140 break;
141 }
142 }
143 }
144 }
145
146 if( is_numeric($value) ) {
147 $value = intval($value);
148 } else {
149 $value = 0;
150 }
151
152 return $value;
153 }
154
155 /**
156 * Delete video position and set it to 0
157 *
158 * @param int $user_id
159 * @param int|string $video_id
160 * @param int $type
161 *
162 * @return void
163 */
164 function delete_video_postion( $user_id, $video_id, $type ) {
165 global $wpdb;
166
167 $legacy_video_id = '';
168 if( !is_numeric($video_id) ) {
169 $legacy_video_id = $video_id;
170 $video_id = 0;
171 } else {
172 $video_id = intval($video_id);
173 }
174
175 $wpdb->update(
176 $wpdb->prefix."fv_player_user_video_positions",
177 array(
178 $type => 0, // dont delete the record, just set the value to 0
179 ),
180 array(
181 'user_id' => $user_id,
182 'video_id' => $video_id,
183 'legacy_video_id' => $legacy_video_id,
184 ),
185 array(
186 '%d',
187 ),
188 array(
189 '%d',
190 '%d',
191 '%s',
192 )
193 );
194
195 // update cache
196 self::set_cache($user_id, 'video');
197 }
198
199 /**
200 * Get player position
201 *
202 * @param int $user_id
203 * @param int $player_id
204 *
205 * @return int;
206 */
207 function get_player_position( $user_id, $player_id ) {
208 global $wpdb;
209
210 $index = 0;
211
212 $cache = self::get_cache($user_id, 'playlist');
213
214 if( is_array($cache) ) {
215 foreach ($cache as $cache_item) {
216 if ($cache_item->player_id == $player_id) {
217 $index = $cache_item->item_index;
218 break;
219 }
220 }
221 }
222
223 if( is_numeric($index) ) {
224 $index = intval($index);
225 } else {
226 $index = 0;
227 }
228
229 return $index;
230 }
231
232 /**
233 * Save video position
234 *
235 * @param int $user_id
236 * @param int|string $video_id
237 * @param string $type
238 * @param int $value
239 *
240 * @return void
241 */
242 function set_video_position( $user_id, $video_id, $type, $value) {
243 global $wpdb;
244
245 $cache = self::get_cache($user_id, 'video');
246 $exits = false;
247
248 if( is_numeric($video_id) ) { // id
249 $video_id = intval($video_id);
250
251 if( is_array($cache) ) {
252 foreach ($cache as $cache_item) {
253 if ($cache_item->video_id == $video_id) {
254 $exits = true;
255 break;
256 }
257 }
258 }
259 } else { // legacy_video_id
260 if( is_array($cache) ) {
261 foreach ($cache as $cache_item) {
262 if ($cache_item->legacy_video_id == $video_id) {
263 $exits = true;
264 break;
265 }
266 }
267 }
268
269 }
270
271 // video id and legacy
272 $legacy_video_id = '';
273 if( !is_numeric($video_id) ) {
274 $legacy_video_id = $video_id;
275 $video_id = 0;
276 }
277
278 // check if the record already exists
279 if( $exits ) { // update position
280 $wpdb->update(
281 $wpdb->prefix."fv_player_user_video_positions",
282 array(
283 $type => $value,
284 ),
285 array(
286 'user_id' => $user_id,
287 'video_id' => $video_id,
288 'legacy_video_id' => $legacy_video_id,
289 )
290 );
291 } else { // insert new position
292 $wpdb->insert(
293 $wpdb->prefix."fv_player_user_video_positions",
294 array(
295 'user_id' => $user_id,
296 'video_id' => $video_id,
297 'legacy_video_id' => $legacy_video_id,
298 $type => $value,
299 )
300 );
301 }
302
303 // update cache
304 self::set_cache($user_id, 'video');
305 }
306
307 /**
308 * Save player position
309 *
310 * @param int $user_id
311 * @param int $player_id
312 * @param int $index
313 *
314 * @return void
315 */
316 function set_player_position( $user_id, $player_id, $index ) {
317 global $wpdb;
318
319 $cache = self::get_cache($user_id, 'playlist');
320 $exits = false;
321
322 if( is_array($cache) ) {
323 // check if the record already exists
324 foreach ($cache as $cache_item) {
325 if ($cache_item->player_id == $player_id) {
326 $exits = true;
327 break;
328 }
329 }
330 }
331
332 if( $exits ) { // update index
333 $wpdb->update(
334 $wpdb->prefix."fv_player_user_playlist_positions",
335 array(
336 'item_index' => $index,
337 ),
338 array(
339 'user_id' => $user_id,
340 'player_id' => $player_id,
341 )
342 );
343 } else { // insert new index
344 $wpdb->insert(
345 $wpdb->prefix."fv_player_user_playlist_positions",
346 array(
347 'user_id' => $user_id,
348 'player_id' => $player_id,
349 'item_index' => $index,
350 )
351 );
352 }
353
354 // update cache
355 self::set_cache($user_id, 'playlist');
356 }
357
358 /**
359 * Get extensionless file name from URL.
360 * For HLS streams take the folder name instead of the file name,
361 * as the file name is often index.m3u8 or stream.m3u8.
362 *
363 * @param string $url
364 *
365 * @return string Video name created from the URL.
366 */
367 public static function get_extensionless_file_name( $url ) {
368 $arr = explode( '/', $url );
369 $video_name = end( $arr );
370
371 // Do not accept HLS playlist file names as these are often index.m3u8 or stream.m3u8
372 // Use folder name instead
373 if ( strpos( $video_name, ".m3u8" ) !== false ) {
374 unset( $arr[ count( $arr ) - 1 ] );
375 $video_name = end( $arr );
376 }
377
378 $video_name = pathinfo( $video_name, PATHINFO_FILENAME );
379
380 $video_name = apply_filters( 'fv_player_position_save_file_name', $video_name, $url );
381
382 return $video_name;
383 }
384
385 public function set_last_positions( $aItem, $index, $aArgs ) {
386
387 if ( did_action( 'wp_ajax_fv_player_db_load' ) ) {
388 return $aItem;
389 }
390
391 global $fv_fp;
392 // we only use the first source to check for stored position,
393 // since other sources would be alternatives (in quality, etc.)
394 if (
395 ( !empty($fv_fp->aCurArgs['saveposition']) || $fv_fp->_get_option('video_position_save_enable') ) &&
396 is_user_logged_in() &&
397 is_array($aItem) &&
398 isset($aItem['sources']) &&
399 isset($aItem['sources'][0])
400 ) {
401
402 // Try with the video ID first
403 $try = array();
404 if( $fv_fp->current_player() ) {
405 $aVideos = $fv_fp->current_player()->getVideos();
406 if( $aVideos && !empty($aVideos[$index]) ) {
407 $try[] = $aVideos[$index]->getId();
408 }
409 }
410 // ...then try with the video filename
411 $try[] = $this->get_extensionless_file_name($aItem['sources'][0]['src']);
412
413 foreach( $try AS $name ) {
414 $metaPosition = $this->get_video_position( get_current_user_id(), $name, 'last_position' );
415
416 if( $metaPosition ) {
417 $aItem['sources'][0]['position'] = $metaPosition;
418 break;
419 }
420 }
421
422 foreach( $try AS $name ) {
423 $metaPosition = $this->get_video_position( get_current_user_id(), $name, 'top_position' );
424
425 if( $metaPosition ) {
426 $aItem['sources'][0]['top_position'] = $metaPosition;
427 break;
428 }
429 }
430
431 foreach( $try AS $name ) {
432 $metaPosition = $this->get_video_position( get_current_user_id(), $name, 'finished' );
433
434 if( $metaPosition ) {
435 $aItem['sources'][0]['saw'] = true;
436 break;
437 }
438 }
439
440 foreach( $try AS $name ) {
441 $metaPositionStart = $this->get_video_position( get_current_user_id(), $name, 'ab_start' );
442 $metaPositionEnd = $this->get_video_position( get_current_user_id(), $name, 'ab_end' );
443
444 // at least one of the values must be set and the start must be smaller than the end
445 if( ( $metaPositionStart || $metaPositionEnd ) && $metaPositionStart < $metaPositionEnd ) {
446 $aItem['sources'][0]['ab_start'] = $metaPositionStart;
447 $aItem['sources'][0]['ab_end'] = $metaPositionEnd;
448 break;
449 }
450 }
451
452 }
453 return $aItem;
454 }
455
456 public function video_position_save() {
457
458 if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'fv_player_video_position_save' ) ) {
459 wp_send_json_error();
460 exit;
461 }
462
463 // TODO: XSS filter for POST values?
464 // check if videoTimes is not a JSON-encoded value, which will happen
465 // when the request came from a navigation.sendBeacon() call instead of the usual AJAX call
466 if( isset( $_POST['videoTimes'] ) ) {
467 // TODO: How to sanitize JSON?
468 $decoded_times = json_decode(urldecode($_POST['videoTimes']), true);
469
470 if ($decoded_times !== false) {
471 $_POST['videoTimes'] = $decoded_times;
472 }
473 }
474
475 if( isset( $_POST['playlistItems'] ) ) {
476 // TODO: How to sanitize JSON?
477 $decoded_playlists = json_decode(urldecode($_POST['playlistItems']), true);
478
479 if ($decoded_playlists !== false) {
480 $_POST['playlistItems'] = $decoded_playlists;
481 }
482 }
483
484 $success = false;
485
486 if ( is_user_logged_in() ) {
487 $uid = get_current_user_id();
488 if ( ! empty( $_POST['videoTimes'] ) ) {
489 foreach ( $_POST['videoTimes'] as $record) {
490 $name = $this->get_extensionless_file_name( sanitize_text_field( $record['name'] ) );
491 if( intval($record['position']) == 0 ) {
492 $this->delete_video_postion($uid, $name, 'last_position');
493 } else {
494 $position = intval($record['position']);
495 $top_position = intval($record['top_position']);
496
497 $previous_position = $this->get_video_position($uid, $name, 'last_position');
498 $previous_top_position = $this->get_video_position($uid, $name, 'top_position');
499 $saw = $this->get_video_position($uid, $name, 'finished');
500 $this->set_video_position($uid, $name, 'last_position', $position);
501
502 // Store the top position if user didn't see the full video
503 // and if it's the same or bigger than what it was before
504 // and if it's bigger than the last position
505 $max = max( array( $previous_top_position, $previous_position, $position, $top_position ) );
506 if( !$saw && $max >= $previous_top_position && $max > $position ) {
507 $this->set_video_position($uid, $name, 'top_position', $max);
508
509 // Otherwise get rid of it
510 } else {
511 $this->delete_video_postion($uid, $name, 'top_position');
512 }
513 }
514
515 // ab loop times
516 if ( ! empty( $record['ab_start'] ) && ! empty( $record['ab_end'] ) ) {
517 $this->set_video_position($uid, $name, 'ab_start', intval($record['ab_start']));
518 $this->set_video_position($uid, $name, 'ab_end', intval($record['ab_end']));
519 } else {
520 $this->delete_video_postion($uid, $name, 'ab_start');
521 $this->delete_video_postion($uid, $name, 'ab_end');
522 }
523
524 // Did the user saw the full video?
525 if( !empty($record['saw']) && sanitize_key( $record['saw'] ) == true ) {
526 $this->set_video_position($uid, $name, 'finished', 1);
527 $this->delete_video_postion($uid, $name, 'top_position');
528 }
529 }
530
531 // What are the videos which user saw in full length?
532 if( !empty($_POST['sawVideo']) && is_array($_POST['sawVideo']) ) {
533 foreach ($_POST['sawVideo'] as $record) {
534
535 // TODO: How does this target a specific video?
536 $this->set_video_position($uid, $name, 'finished', 1);
537 $this->delete_video_postion($uid, $name, 'top_position');
538 }
539 }
540
541 $success = true;
542 }
543
544 if ( ! empty( $_POST['playlistItems'] ) ) {
545 foreach( $_POST['playlistItems'] as $playeritem ) {
546 $this->set_player_position( absint( $uid ), absint( $playeritem['player'] ), absint( $playeritem['item'] ) );
547 }
548
549 $success = true;
550 }
551
552 if( $success ) {
553 wp_send_json_success();
554 }
555 } else {
556 wp_send_json_error();
557 }
558 }
559
560 function player_position_save_admin_default_options_html() {
561 global $fv_fp;
562 $fv_fp->_get_checkbox(__( 'Remember video position', 'fv-player' ), 'video_position_save_enable', __('Stores the last video play position for users, so they can continue watching from where they left.'), __('It stores in <code>wp_usermeta</code> database table for logged in users and in a localStorage or cookie for guest users.'));
563 }
564
565 function shortcode( $attributes, $media, $fv_fp ) {
566
567 if ( did_action( 'wp_ajax_fv_player_db_load' ) ) {
568 return $attributes;
569 }
570
571 if( !empty($fv_fp->aCurArgs['saveposition']) ) {
572 $attributes['data-save-position'] = $fv_fp->aCurArgs['saveposition'];
573 }
574
575 if ( $fv_fp->_get_option('video_position_save_enable') || !empty($fv_fp->aCurArgs['saveposition']) ) {
576 $player_id = false;
577
578 if( $fv_fp->current_player() ) { // db player
579 $player_id = $fv_fp->current_player()->getId();
580 }
581
582 if( $player_id ) { // add id to data item if db player
583 $attributes['data-player-id'] = $player_id;
584
585 $user_id = get_current_user_id();
586 if( $user_id ) {
587 $metaItem = $this->get_player_position($user_id, $player_id);
588
589 if ( $metaItem >= 0 ) {
590 // playlist item restore
591 $attributes['data-playlist_start'] = intval($metaItem) + 1; // playlist-start-position module starts from 0
592 }
593 }
594 }
595 }
596
597 return $attributes;
598 }
599 }
600
601 global $FV_Player_Position_Save;
602 $FV_Player_Position_Save = new FV_Player_Position_Save();
603