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 / db-video.php

db-video.php in FV Player 8 trunk, at models/db-video.php

1,384 lines 41.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /* FV Player - HTML5 video player
3 Copyright (C) 2013 Foliovision
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 // video instance with options that's stored in a DB
20 class FV_Player_Db_Video {
21
22 private
23 $id, // automatic ID for the video
24 $is_valid = false, // used when loading the video from DB to determine whether we've found it
25 $title, // optional video title
26 $title_hide, // optional video title
27 $end, // allows you to show only a specific part of a video
28 $mobile, // mobile (smaller-sized) version of this video
29 $rtmp, // optional RTMP server URL
30 $rtmp_path, // if RTMP is set, this will have the path on the server to the RTMP stream
31 $splash, // URL to the splash screen picture
32 $splash_text, // an optional splash screen text
33 $splash_attachment_id, // splash attachment id
34 $src, // the main video source
35 $src1, // alternative source path #1 for the video
36 $src2, // alternative source path #2 for the video
37 $start, // allows you to show only a specific part of a video
38 $width,
39 $height,
40 $aspect_ratio,
41 $duration,
42 $live,
43 $toggle_advanced_settings,
44 $last_check,
45 $meta_data = null; // object of this video's meta data
46
47 private static
48 $db_table_name,
49 $DB_Instance = null;
50
51 /**
52 * @return int
53 */
54 public function getId() {
55 return $this->id;
56 }
57
58 /**
59 * @return float
60 */
61 public function getAspectRatio() {
62 return floatval($this->aspect_ratio);
63 }
64
65 /**
66 * @return string
67 */
68 public function getCaption() {
69 _deprecated_function( __METHOD__, '8.0', __CLASS__ . '::getTitle' );
70
71 return $this->getTitle();
72 }
73
74 /**
75 * @return string
76 */
77 public function getCaptionFromSrc() {
78 _deprecated_function( __METHOD__, '8.0', __CLASS__ . '::getTitleFromSrc' );
79
80 return $this->getTitleFromSrc();
81 }
82
83 /**
84 * @return int
85 */
86 public function getDuration() {
87 if( $this->duration ) {
88 return intval($this->duration);
89 }
90
91 // TODO: Conversion process
92 return intval($this->getMetaValue('duration',true));
93 }
94
95 /**
96 * @return string
97 */
98 public function getEnd() {
99 return flowplayer::hms_to_seconds($this->end);
100 }
101
102 /**
103 * @return int
104 */
105 public function getHeight() {
106 return intval($this->height);
107 }
108
109 /**
110 * @return int
111 */
112 public function getLastCheck() {
113 return ! empty( $this->last_check ) ? strtotime( $this->last_check ) : false;
114 }
115
116 /**
117 * @return bool
118 */
119 public function getLive() {
120 return boolval($this->live);
121 }
122
123 /**
124 * @return bool
125 */
126 public function getToggleAdvancedSettings() {
127 return boolval($this->toggle_advanced_settings);
128 }
129
130 /**
131 * @return string
132 */
133 public function getMobile() {
134 return $this->mobile;
135 }
136
137 /**
138 * @return string
139 */
140 public function getRtmp() {
141 return $this->rtmp;
142 }
143
144 /**
145 * @return string
146 */
147 public function getRtmpPath() {
148 return $this->rtmp_path;
149 }
150
151 /**
152 * @return string
153 */
154 public function getSplash() {
155 return $this->splash;
156 }
157
158 /**
159 * @return string
160 */
161 public function getSplashText() {
162 return $this->splash_text;
163 }
164
165 /**
166 * @return int
167 */
168 public function getSplashAttachmentId() {
169 return $this->splash_attachment_id;
170 }
171
172 /**
173 * @return string
174 */
175 public function getSrc() {
176 return $this->src;
177 }
178
179 /**
180 * @return string
181 */
182 public function getSrc1() {
183 return $this->src1;
184 }
185
186 /**
187 * @return string
188 */
189 public function getSrc2() {
190 return $this->src2;
191 }
192
193 /**
194 * @return string
195 */
196 public function getStart() {
197 return flowplayer::hms_to_seconds($this->start);
198 }
199
200 /**
201 * @return string
202 */
203 public function getTitle() {
204 return $this->title;
205 }
206
207 /**
208 * @return bool
209 */
210 public function getTitleHide() {
211 return boolval( $this->title_hide );
212 }
213
214 /**
215 * @return string
216 */
217 public function getTitleFromSrc() {
218 $title = flowplayer::get_title_from_src($this->getSrc());
219
220 $title = apply_filters( 'fv_flowplayer_caption_src', $title , $this->getSrc(), $this );
221 $title = apply_filters( 'fv_flowplayer_title_src', $title , $this->getSrc(), $this );
222
223 return urldecode($title);
224 }
225
226 /**
227 * @return int
228 */
229 public function getWidth() {
230 return intval($this->width);
231 }
232
233 /**
234 * @return bool
235 */
236 public function getIsValid() {
237 return $this->is_valid;
238 }
239
240 /**
241 * Initializes database name, including WP prefix
242 * once WPDB class is initialized.
243 *
244 * @return string Returns the actual table name for this ORM class.
245 */
246 public static function init_db_name() {
247 global $wpdb;
248
249 self::$db_table_name = $wpdb->prefix.'fv_player_videos';
250 return self::$db_table_name;
251 }
252
253 /**
254 * Returns name of the video DB table.
255 *
256 * @return mixed The name of the video DB table.
257 */
258 public static function get_db_table_name() {
259 if ( !self::$db_table_name ) {
260 self::init_db_name();
261 }
262
263 return self::$db_table_name;
264 }
265
266 /**
267 * Checks for DB tables existence and creates it as necessary.
268 *
269 * @param $force Forces to run dbDelta.
270 */
271 public static function initDB($force = false) {
272 global $wpdb, $fv_fp, $fv_wp_flowplayer_ver;
273
274 self::init_db_name();
275
276 $res = false;
277
278 if( defined('PHPUnitTestMode') || !$fv_fp->_get_option('video_model_db_checked') || $fv_fp->_get_option('video_model_db_checked') != $fv_wp_flowplayer_ver || $force ) {
279 $sql = "
280 CREATE TABLE " . self::$db_table_name . " (
281 id bigint(20) unsigned NOT NULL auto_increment,
282 src varchar(1024) NOT NULL,
283 src1 varchar(1024) NOT NULL,
284 src2 varchar(1024) NOT NULL,
285 splash_attachment_id bigint(20) unsigned,
286 splash varchar(1024) NOT NULL,
287 splash_text varchar(512) NOT NULL,
288 title varchar(1024) NOT NULL,
289 title_hide varchar(7) NOT NULL,
290 end varchar(16) NOT NULL,
291 mobile varchar(512) NOT NULL,
292 rtmp varchar(128) NOT NULL,
293 rtmp_path varchar(128) NOT NULL,
294 start varchar(16) NOT NULL,
295 width int(11) NOT NULL,
296 height int(11) NOT NULL,
297 aspect_ratio varchar(8) NOT NULL,
298 duration decimal(7,2) NOT NULL,
299 live tinyint(1) NOT NULL,
300 toggle_advanced_settings varchar(7) NOT NULL,
301 last_check datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
302 PRIMARY KEY (id),
303 KEY src (src(191)),
304 KEY title (title(191))
305 )" . $wpdb->get_charset_collate() . ";";
306 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
307 $res = dbDelta( $sql );
308
309 if( $fv_fp->_get_option('video_model_db_checked') != $fv_wp_flowplayer_ver ) {
310 self::updateTableConversion();
311 }
312
313 $fv_fp->_set_option('video_model_db_checked', $fv_wp_flowplayer_ver);
314 }
315
316 return $res;
317 }
318
319 /**
320 * Run conversion of old data to new data structure.
321 *
322 * @return void
323 */
324 private static function updateTableConversion() {
325 global $wpdb, $fv_fp;
326
327 $table = self::$db_table_name;
328 $meta_table = $wpdb->prefix.'fv_player_videometa';
329
330 if( $fv_fp->_get_option('video_model_db_updated') != '7.9.3' ) {
331 if ( $wpdb->get_results( $wpdb->prepare( "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = %s AND column_name = 'caption'", $table ) ) ) {
332 $wpdb->query( "UPDATE `{$wpdb->prefix}fv_player_videos` SET title = caption WHERE title = '' AND caption != ''" );
333 }
334
335 if( $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $meta_table ) ) == $meta_table ) {
336 // update duration
337 $wpdb->query("UPDATE `{$wpdb->prefix}fv_player_videos` AS v JOIN `{$wpdb->prefix}fv_player_videometa` AS m ON v.id = m.id_video SET duration = CAST( m.meta_value AS DECIMAL(7,2) ) WHERE meta_key = 'duration' AND meta_value > 0");
338
339 // update live
340 $wpdb->query("UPDATE `{$wpdb->prefix}fv_player_videos` AS v JOIN `{$wpdb->prefix}fv_player_videometa` AS m ON v.id = m.id_video SET live = 1 WHERE meta_key = 'live' AND (meta_value = 1 OR meta_value = 'true')");
341
342 // update last_check
343 $wpdb->query("UPDATE `{$wpdb->prefix}fv_player_videos` AS v JOIN `{$wpdb->prefix}fv_player_videometa` AS m ON v.id = m.id_video SET last_check = FROM_UNIXTIME(meta_value, '%Y-%m-%d %H:%i:%s') WHERE meta_key = 'last_video_meta_check' AND meta_value > 0 AND (meta_value IS NOT NULL OR meta_value != '')");
344
345 $wpdb->query("UPDATE `{$wpdb->prefix}fv_player_videos` SET toggle_advanced_settings = 'true' WHERE src1 != '' OR src2 != '' OR mobile != '' OR rtmp != '' OR rtmp_path != ''");
346 }
347
348 $fv_fp->_set_option('video_model_db_updated', '7.9.3');
349 }
350 }
351
352 /**
353 * FV_Player_Db_Video constructor.
354 *
355 * @param int $id ID of video to load data from the DB for.
356 * @param array $options Options for a newly created video that will be stored in a DB.
357 * @param FV_Player_Db $DB_Cache Instance of the DB shortcode global object that handles caching
358 * of videos, players and their meta data.
359 *
360 * @throws Exception When no valid ID nor options are provided.
361 */
362 function __construct($id, $options = array(), $DB_Cache = null) {
363 global $wpdb;
364
365 if ($DB_Cache) {
366 self::$DB_Instance = $DB_Cache;
367 } else {
368 global $FV_Player_Db;
369 self::$DB_Instance = $DB_Cache = $FV_Player_Db;
370 }
371
372 self::initDB();
373
374 // TODO: This should not be here at all
375 $multiID = is_array($id);
376
377 // don't load anything, if we've only created this instance
378 // to initialize the database (this comes from list-table.php and unit tests)
379 if ($id === -1) {
380 return;
381 }
382
383 // if we've got options, fill them in instead of querying the DB,
384 // since we're storing new video into the DB in such case
385 if (is_array($options) && count($options) ) {
386 foreach ($options as $key => $value) {
387 if( $key == 'meta' ) continue; // meta is handled elsewhere, but it's part of the object when importing from JSON
388
389 if (property_exists($this, $key)) {
390 if ($key !== 'id') {
391 if( !is_string($value) ) $value = '';
392
393 $this->$key = wp_strip_all_tags( FV_Player_Db::sanitize( $value ) );
394 } else {
395 // ID cannot be set, as it's automatically assigned to all new videos
396 trigger_error('ID of a newly created DB video was provided but will be generated automatically.');
397 }
398 }
399 }
400
401 $this->is_valid = true;
402 } else if ($multiID || (is_numeric($id) && $id > 0)) {
403 /* @var $cache FV_Player_Db_Video[] */
404 $cache = ($DB_Cache ? $DB_Cache->getVideosCache() : array());
405 $all_cached = false;
406
407 // no options, load data from DB
408 if ($multiID) {
409 // make sure we have numeric IDs and that they're not cached yet
410 $query_ids = array();
411 foreach ($id as $id_key => $id_value) {
412 if (!isset($cache[$id_value])) {
413 $query_ids[ $id_key ] = $id_value;
414 }
415
416 $id[ $id_key ] = $id_value;
417 }
418
419 // load multiple videos via their IDs but a single query and return their values
420 if (count($query_ids)) {
421 $placeholders = implode( ', ', array_fill( 0, count( $query_ids ), '%d' ) );
422
423 $video_data = $wpdb->get_results(
424 $wpdb->prepare(
425 // $placeholders is a string of %d created above
426 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
427 "SELECT * FROM `{$wpdb->prefix}fv_player_videos` WHERE id IN( $placeholders )",
428 $query_ids
429 )
430 );
431
432 if( !$video_data && count($id) != count($query_ids) ) { // if no video data has returned, but we have the rest of videos cached already
433 $all_cached = true;
434 }
435 } else {
436 $all_cached = true;
437 }
438 } else {
439 if (!isset($cache[$id])) {
440 $video_data = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM `{$wpdb->prefix}fv_player_videos` WHERE id = %d", $id ) );
441
442 } else {
443 $all_cached = true;
444 }
445 }
446
447 if (isset($video_data) && $video_data && (is_object($video_data) || count($video_data))) {
448 // single ID, just populate our own data
449 if (!$multiID) {
450 // fill-in our internal variables, as they have the same name as DB fields (ORM baby!)
451 foreach ( $video_data as $key => $value ) {
452 // Do not set the caption if it's still in DB table.
453 if ( 'caption' === $key ) {
454 // Use it as the title if there is none.
455 if ( empty( $this->title ) ) {
456 $this->title = $value;
457 }
458 continue;
459 }
460 $this->$key = FV_Player_Db::sanitize( $value );
461 }
462
463 // cache this video in DB object
464 if ($DB_Cache) {
465 $cache[$this->id] = $this;
466 }
467 } else {
468 // multiple IDs, create new video objects for each of them except the first one,
469 // for which we'll use this instance
470 $first_done = false;
471 foreach ($video_data as $db_record) {
472 if (!$first_done) {
473 // fill-in our internal variables
474 foreach ( $db_record as $key => $value ) {
475 // Do not set the caption if it's still in DB table.
476 if ( 'caption' === $key ) {
477 // Use it as the title if there is none.
478 if ( empty( $db_record->title ) ) {
479 $db_record->title = $value;
480 }
481 continue;
482 }
483 $this->$key = FV_Player_Db::sanitize( $value );
484 }
485
486 $first_done = true;
487
488 // cache this video in DB object
489 if ($DB_Cache) {
490 $cache[$this->id] = $this;
491 }
492 } else {
493 // create a new video object and populate it with DB values
494 $record_id = $db_record->id;
495 // if we don't unset this, we'll get warnings
496 unset($db_record->id);
497
498 if ($DB_Cache && !$DB_Cache->isVideoCached($record_id)) {
499 $video_object = new FV_Player_Db_Video( null, get_object_vars( $db_record ), self::$DB_Instance );
500 $video_object->link2db( $record_id );
501
502 // cache this video in DB object
503 if ( $DB_Cache ) {
504 $cache[ $record_id ] = $video_object;
505 }
506 }
507 }
508 }
509 }
510 $this->is_valid = true;
511 } else if ($all_cached) {
512 // fill the data for this class with data of the cached class
513 if ($multiID) {
514 $cached_video = $cache[reset($id)];
515 } else {
516 $cached_video = $cache[$id];
517 }
518
519 if ( $cached_video ) {
520 foreach ($cached_video->getAllDataValues() as $key => $value) {
521 $this->$key = FV_Player_Db::sanitize( $value );
522 }
523
524 // add meta data
525 $this->meta_data = $cached_video->getMetaData();
526
527 // make this class a valid video
528 $this->is_valid = true;
529 }
530 }
531 } else {
532 throw new Exception('No options nor a valid ID was provided for DB video instance.');
533 }
534
535 // update cache, if changed
536 if (isset($cache) && (!isset($all_cached) || !$all_cached)) {
537 self::$DB_Instance->setVideosCache($cache);
538 }
539 }
540
541 /**
542 * Makes this video linked to a record in database.
543 *
544 * This is used when loading multiple videos in the constructor,
545 * so we can return them as objects from the DB and any saving will
546 * not insert their duplicates.
547 *
548 * Also used when updating video via editor, so we keep certain fields.
549 *
550 * @param int $id The DB ID to which we'll link this video.
551 * @param bool $load_meta If true, the meta data will be loaded for the video from database.
552 * Used when loading multiple videos at once with the array $id constructor parameter.
553 *
554 * @throws Exception When the underlying Meta object throws.
555 */
556 public function link2db($id, $load_meta = false) {
557 $this->id = (int) $id;
558
559 // Some fields are not present in editor and need to load from DB
560 $fields = array(
561 'width',
562 'height',
563 'aspect_ratio',
564 'duration',
565 'last_check'
566 );
567
568 $missing_something = false;
569 foreach( $fields AS $field ) {
570 if( empty($this->$field) ) {
571 $missing_something = true;
572 break;
573 }
574 }
575
576 if( $missing_something ) {
577 $objVideo = new FV_Player_Db_Video( $id, array(), self::$DB_Instance );
578 foreach( $fields AS $field ) {
579 if( empty($this->$field) ) {
580 $this->$field = $objVideo->$field;
581 }
582 }
583 }
584
585 if ($load_meta) {
586 $this->meta_data = new FV_Player_Db_Video_Meta(null, array('id_video' => array($id)), self::$DB_Instance);
587 }
588 }
589
590 /**
591 * This method will manually link meta data to the video.
592 * Used when not using save() method to link meta data to video while saving it
593 * into the database (i.e. while previewing etc.)
594 *
595 * @param FV_Player_Db_Video_Meta $meta_data The meta data object to link to this video.
596 *
597 * @throws Exception When an underlying meta data object throws an exception.
598 */
599 public function link2meta($meta_data) {
600 if (is_array($meta_data) && count($meta_data)) {
601 // we have meta, let's insert that
602 $first_done = false;
603 foreach ($meta_data as $meta_record) {
604 // create new record in DB
605 $meta_object = new FV_Player_Db_Video_Meta(null, $meta_record, self::$DB_Instance);
606
607 // link to DB, if the meta record has an ID
608 if (!empty($meta_record['id'])) {
609 $meta_object->link2db($meta_record['id']);
610 }
611
612 if (!$first_done) {
613 $this->meta_data = array($meta_object);
614 $first_done = true;
615 } else {
616 $this->meta_data[] = $meta_object;
617 }
618 }
619 } else if ($meta_data === -1) {
620 $this->meta_data = -1;
621 }
622 }
623
624 /**
625 * Searches for a player video via custom query.
626 * Used in plugins such as HLS which will
627 * provide video src data but not ID to search for.
628 *
629 * @param bool $like The LIKE part for the database query. If false, exact match is used.
630 * @param null $fields Fields to return for this search. If null, all fields are returned.
631 *
632 * @return bool Returns true if any data were loaded, false otherwise.
633 */
634 public function searchBySrc($like = false, $fields = null) {
635 _deprecated_function( __FUNCTION__, '7.5.22', 'FV_Player_Db::query_videos' );
636
637 global $wpdb;
638
639 if ( $like ) {
640 $row = $wpdb->get_row(
641 $wpdb->prepare(
642 "SELECT * FROM `{$wpdb->prefix}fv_player_videos` WHERE src LIKE %s ORDER BY id DESC",
643 '%' . $wpdb->esc_like( $this->src ) . '%'
644 )
645 );
646
647 } else {
648 $row = $wpdb->get_row(
649 $wpdb->prepare( "SELECT * FROM `{$wpdb->prefix}fv_player_videos` WHERE src = %s ORDER BY id DESC", $this->src )
650 );
651 }
652
653 if (!$row) {
654 return false;
655 } else {
656 // load up all values for this video
657 foreach ($row as $key => $value) {
658 if (property_exists($this, $key)) {
659 $this->$key = FV_Player_Db::sanitize( $value );
660 }
661 }
662
663 return true;
664 }
665 }
666
667 function find_video_meta_inputs( &$video_meta_inputs, $input ) {
668 if ( is_array( $input ) ) {
669 if ( ! empty( $input['name'] ) ) {
670
671 if ( ! empty( $input['video_meta'] ) ) {
672 $video_meta_inputs[] = $input['name'];
673 if ( ! empty( $input['language'] ) ) {
674 $video_meta_inputs[] = $input['name'] . '_*';
675 }
676 }
677
678 if ( ! empty( $input['children'] ) ) {
679 $this->find_video_meta_inputs( $video_meta_inputs, $input['children'] );
680 }
681 return;
682 }
683
684 foreach ( $input as $v ) {
685 $this->find_video_meta_inputs( $video_meta_inputs, $v );
686 }
687 }
688 }
689
690 /**
691 * Returns all options data for this video.
692 *
693 * @return array Returns all options data for this video.
694 */
695 public function getAllDataValues() {
696 $data = array();
697 foreach (get_object_vars($this) as $property => $value) {
698 if ($property != 'is_valid' && $property != 'db_table_name' && $property != 'DB_Instance' && $property != 'meta_data') {
699 if ( 'live' === $property ) {
700 $value = $value ? true : false;
701 }
702
703 $data[$property] = $value;
704 }
705 }
706
707 return $data;
708 }
709
710 /**
711 * Returns meta data for this video.
712 *
713 * @return FV_Player_Db_Video_Meta[] Returns all meta data for this video.
714 * @throws Exception When an underlying meta data object throws an exception.
715 */
716 public function getMetaData() {
717 // meta data already loaded and present, return them
718 if ($this->meta_data && $this->meta_data !== -1) {
719 // meta data will be an array if we filled all of them at once
720 // from database at the time when player is initially created
721 if (is_array($this->meta_data)) {
722 return $this->meta_data;
723 } else if ( self::$DB_Instance->isVideoMetaCached($this->id) ) {
724 $cache = self::$DB_Instance->getVideoMetaCache();
725 return $cache[$this->id];
726 } else {
727 if ($this->meta_data && $this->meta_data->getIsValid()) {
728 return array( $this->meta_data );
729 } else {
730 return array();
731 }
732 }
733
734 // meta data not loaded yet - load them now if the player exists
735 } else if ($this->meta_data === null && $this->getId() ) {
736
737 $this->meta_data = new FV_Player_Db_Video_Meta(null, array('id_video' => array( $this->getId() ) ), self::$DB_Instance);
738
739 // set meta data to -1, so we know we didn't get any meta data for this video
740 if (!$this->meta_data->getIsValid()) {
741 $this->meta_data = -1;
742 return array();
743 } else {
744 if ($this->meta_data && $this->meta_data->getIsValid()) {
745 // meta data will be an array if we filled all of them at once
746 // from database at the time when player is initially created
747 if (is_array($this->meta_data)) {
748 return $this->meta_data;
749 } else if ( self::$DB_Instance->isVideoMetaCached($this->id) ) {
750 $cache = self::$DB_Instance->getVideoMetaCache();
751 return $cache[$this->id];
752 } else {
753 if ($this->meta_data && $this->meta_data->getIsValid()) {
754 return array( $this->meta_data );
755 } else {
756 return array();
757 }
758 }
759 } else {
760 return array();
761 }
762 }
763 } else {
764 return array();
765 }
766 }
767
768 /**
769 * Returns actual meta data for a key for this video.
770 */
771 public function getMetaValue( $key, $single = false ) {
772 $output = array();
773 $data = $this->getMetaData();
774 if (count($data)) {
775 foreach ($data as $meta_object) {
776 if ($meta_object->getMetaKey() == $key) {
777 if( $single ) return $meta_object->getMetaValue();
778 $output[] = $meta_object->getMetaValue();
779 }
780 }
781 }
782 if( $single ) return false;
783 return $output;
784 }
785
786 /**
787 * Updates or instert a video meta row
788 *
789 * @param string $key The meta key
790 * @param string $value The meta value
791 * @param int $id ID of the existing video meta row.
792 * If it's left empty only one $key is allowed for the $this->getId() video ID.
793 *
794 * @throws Exception When the underlying Meta object throws.
795 *
796 * @return bool|int Returns record ID if successful, false otherwise.
797 *
798 */
799 public function updateMetaValue( $key, $value, $id = false ) {
800 $to_update = false;
801 $data = $this->getMetaData();
802
803 if (count($data)) {
804 foreach ($data as $meta_object) {
805 // find the matching video meta row and if id is provided as well, match on that too
806 if( ( !$id || $id == $meta_object->getId() ) && $meta_object->getMetaKey() == $key) {
807 $to_update = $meta_object->getId();
808
809 // if there is no change, then do not run any update and instead return the row ID
810 if(
811 is_string($value) && strcmp($meta_object->getMetaValue(), $value) == 0 ||
812 !is_string($value) && $meta_object->getMetaValue() == $value
813 ) {
814 return $to_update;
815 }
816 }
817 }
818 }
819
820 // if matching row has been found or if it was not found and no row id is provided (insert)
821 if( $to_update || !$to_update && !$id ) {
822 $meta = new FV_Player_Db_Video_Meta( null, array( 'id_video' => $this->getId(), 'meta_key' => $key, 'meta_value' => $value ), self::$DB_Instance );
823 if( $to_update ) $meta->link2db($to_update);
824 return $meta->save();
825 }
826
827 return false;
828 }
829
830 /**
831 * Lets you alter any of the video properties and then call save()
832 *
833 * @param string $key The meta key
834 * @param string $value The meta value
835 */
836 public function set( $key, $value ) {
837 $this->$key = FV_Player_Db::sanitize( $value );
838 }
839
840 /**
841 * Stores new video instance or updates and existing one
842 * in the database.
843 *
844 * @param array $meta_data An optional array of key-value objects
845 * with possible meta data for this video.
846 * @param bool $skip_meta If true, meta data will not be processed. Used in
847 * fv_player_guttenberg_attributes_save().
848 * We need this as empty $meta_data would mean it should
849 * all be removed.
850 * @param bool $skip_video_meta_check If true, video meta check will be skipped.
851 *
852 * @return bool|int Returns record ID if successful, false otherwise.
853 * @throws Exception When the underlying metadata object throws.
854 */
855 public function save( $meta_data = array(), $skip_meta = false, $skip_video_meta_check = false ) {
856 global $wpdb;
857
858 $is_update = ($this->id ? true : false);
859
860 // Save new video early so that we can attach video meta to the new video ID
861 if ( ! $is_update ) {
862 $this->save_do( $is_update );
863
864 // Bail if we encounter an error
865 if ( $wpdb->last_error ) {
866 return false;
867 }
868 }
869
870 $video_url = $this->getSrc();
871
872 /*
873 * Check video duration, fetch splash screen and title
874 */
875 $last_video_meta_check = $this->getLastCheck();
876 $last_video_meta_check_src = $this->getMetaValue( 'last_video_meta_check_src', true );
877
878 // Look if the last_video_meta_check_src is just about to save
879 if ( ! $last_video_meta_check_src && ! empty( $meta_data ) ) {
880 foreach( $meta_data AS $meta ) {
881 if ( 'last_video_meta_check_src' === $meta['meta_key'] ) {
882 $last_video_meta_check_src = $meta['meta_value'];
883 break;
884 }
885 }
886 }
887
888 // Check video duration, or even splash image and title if it was not checked previously
889 if(
890 ! $skip_video_meta_check &&
891 (
892 !$last_video_meta_check ||
893 $last_video_meta_check + 900 < time() ||
894 strcasecmp( $video_url, $last_video_meta_check_src ) != 0
895 )
896 ) {
897
898 // Check if FV Player Pro can fetch the video splash, title and duration
899 $video_data = apply_filters('fv_player_meta_data', $video_url, false, $this);
900
901 // No information obtained, do a basic check
902 if( !is_array($video_data) ) {
903 $video_data = array();
904
905 // was only the file path provided?
906 $parsed = wp_parse_url($video_url);
907 if( count($parsed) == 1 && !empty($parsed['path']) ) {
908 // then user the WordPress home URL
909 $video_url = home_url($video_url);
910 // but remove the "path" if WordPress runs in a folder
911 $wordpress_home = wp_parse_url(home_url());
912 if( !empty($wordpress_home['path']) ) {
913 $video_url = str_replace( $wordpress_home['path'], '', $video_url );
914 }
915 }
916
917 // only run the actual check for real URLs
918 if( filter_var($video_url, FILTER_VALIDATE_URL) && ! defined('PHPUnitTestMode') ) {
919
920 global $FV_Player_Checker;
921 $check = $FV_Player_Checker->check_mimetype( array($video_url), false, true );
922 foreach( array(
923 'aspect_ratio',
924 'duration',
925 'error',
926 'height',
927 'is_audio',
928 'is_encrypted',
929 'author_thumbnail',
930 'author_name',
931 'author_url',
932 'is_live',
933 'width'
934 ) AS $key ) {
935 if( !empty($check[$key]) ) {
936 $video_data[$key] = $check[$key];
937 }
938 }
939 }
940 }
941
942 if( is_array($video_data) ) {
943 $video_data = wp_parse_args( $video_data,
944 array(
945 'error' => false,
946 'duration' => false,
947 'is_live' => false,
948 'is_audio' => false,
949 'width' => false,
950 'height' => false,
951 'aspect_ratio' => false,
952 'chapters' => false,
953 )
954 );
955
956 // TODO: process chapters and also error, is_live, is_audio
957 // TODO: check caption, splash, chapters, auto_splash, auto_caption and also error, is_live, is_audio
958
959 $this->last_check = current_time( 'mysql', true );
960
961 if( $video_data['error'] ) {
962 $this->updateMetaValue( 'error', $video_data['error'] );
963
964 $last_error_count = $this->getMetaValue( 'error_count', true );
965
966 if( empty($last_error_count) ) $last_error_count = 0;
967 $last_error_count++;
968
969 $this->updateMetaValue( 'error_count', $last_error_count );
970
971 } else {
972 $this->deleteMetaValue( 'error' );
973 $this->deleteMetaValue( 'error_count' );
974 }
975
976 if( $video_data['width'] ) {
977 $this->width = intval($video_data['width']);
978 } else {
979 $this->width = false;
980 }
981
982 if( $video_data['height'] ) {
983 $this->height = intval($video_data['height']);
984 } else {
985 $this->height = false;
986 }
987
988 if( $video_data['aspect_ratio'] ) {
989 $this->aspect_ratio = round( floatval($video_data['aspect_ratio']), 4 );
990 } else if( intval($this->width) > 0 && $this->height ) {
991 $this->aspect_ratio = round( $this->height/$this->width, 4 );
992 } else {
993 $this->aspect_ratio = false;
994 }
995
996 if( $video_data['duration'] ) {
997 $this->duration = $video_data['duration'];
998
999 // Remove the legacy value stored in video meta
1000 $this->deleteMetaValue( 'duration' );
1001 } else if ( empty( $this->duration ) ) {
1002 $this->duration = 0;
1003 }
1004
1005 if( $video_data['is_live'] ) {
1006 $this->live = true;
1007 } else {
1008 $this->live = false;
1009 }
1010
1011 // Remove the legacy value stored in video meta
1012 $this->deleteMetaValue( 'live' );
1013
1014 if( !empty($video_data['is_encrypted']) ) {
1015 $this->updateMetaValue( 'encrypted', true );
1016
1017 } else {
1018 $this->deleteMetaValue( 'encrypted' );
1019 }
1020
1021 if( !empty($video_data['author_thumbnail']) ) {
1022 $this->updateMetaValue( 'author_thumbnail', $video_data['author_thumbnail'] );
1023
1024 } else {
1025 $this->deleteMetaValue( 'author_thumbnail' );
1026 }
1027
1028 if( !empty($video_data['author_name']) ) {
1029 $this->updateMetaValue( 'author_name', $video_data['author_name'] );
1030
1031 } else {
1032 $this->deleteMetaValue( 'author_name' );
1033 }
1034
1035 if( !empty($video_data['author_url']) ) {
1036 $this->updateMetaValue( 'author_url', $video_data['author_url'] );
1037
1038 } else {
1039 $this->deleteMetaValue( 'author_url' );
1040 }
1041
1042 if( !empty($video_data['chapters']) && ! $this->getMetaValue( 'chapters', true ) ) {
1043 $this->updateMetaValue( 'chapters', $video_data['chapters'] );
1044 }
1045
1046 if( !empty($video_data['is_audio']) ) {
1047 $this->updateMetaValue( 'audio', true );
1048
1049 } else {
1050 $this->deleteMetaValue( 'audio' );
1051 }
1052
1053 if( !empty($video_data['name']) && (
1054 !$this->getTitle() /* || $this->getMetaValue( 'auto_caption', true ) */
1055 ) ) {
1056 $this->title = $video_data['name'];
1057
1058 // $this->updateMetaValue( 'auto_caption', 1 );
1059 }
1060
1061 if( !empty($video_data['synopsis']) && !$this->getMetaValue( 'synopsis', true ) ) {
1062 $synopsis = $video_data['synopsis'];
1063
1064 $this->updateMetaValue( 'synopsis', $synopsis );
1065 }
1066
1067 if( !empty($video_data['thumbnail']) && (
1068 !$this->getSplash() /* || $this->getMetaValue( 'auto_splash', true ) */
1069 ) ) {
1070 $this->splash = $video_data['thumbnail'];
1071
1072 // $this->updateMetaValue( 'auto_splash', 1 );
1073 }
1074
1075 if( !empty($video_data['splash_attachment_id']) ) {
1076 $this->splash_attachment_id = $video_data['splash_attachment_id'];
1077 }
1078 } else {
1079 $meta_data = array();
1080 }
1081
1082 if ( $last_video_meta_check_src !== $video_url ) {
1083 $this->updateMetaValue( 'last_video_meta_check_src', $video_url );
1084 }
1085
1086 }
1087
1088 /*
1089 * If the splash has changed make sure the old Media Library item is no longer linked (postmeta) to this video
1090 */
1091 $splash_attachment_id = $this->getSplashAttachmentId();
1092
1093 if( $is_update ) {
1094 // check if splash url changed
1095 if( !empty( $splash_attachment_id ) ) {
1096 $saved_splash = wp_get_attachment_image_url($splash_attachment_id, 'full', false);
1097 if( !empty( $saved_splash ) ) {
1098 $saved_parse = wp_parse_url( $saved_splash );
1099 $current_parse = $this->getSplash() ? wp_parse_url( $this->getSplash() ) : false;
1100
1101 // if splash removed or changed, delete splash attachment
1102 if( !$current_parse || (strcmp( $saved_parse['path'], $current_parse['path'] ) !== 0) ) {
1103 delete_post_meta( $splash_attachment_id, 'fv_player_video_id', $this->getId() );
1104 $this->splash_attachment_id = '';
1105 }
1106 }
1107 }
1108 }
1109
1110 $this->save_do( true );
1111
1112 if (!$wpdb->last_error) {
1113
1114 // Get all registered input names which belong to video meta
1115 $video_meta_inputs = array();
1116
1117 if ( ! function_exists( 'fv_player_editor_video_fields' ) ) {
1118 require_once( dirname( __FILE__ ) . '/../controller/editor.php' );
1119 }
1120
1121 foreach (
1122 array(
1123 fv_player_editor_video_fields(),
1124 fv_player_editor_subtitle_fields()
1125 ) as $fields
1126 ) {
1127 $this->find_video_meta_inputs( $video_meta_inputs, $fields );
1128 }
1129
1130 if ( ! $skip_meta ) {
1131
1132 // we check which meta values are no longer set and remove these
1133 $existing_meta = $is_update ? $this->getMetaData() : array();
1134 $existing_meta_ids = array();
1135 foreach( $existing_meta as $existing ) {
1136
1137 // video meta fields which do not have an input field should be kept
1138 $should_skip = true;
1139 foreach ( $video_meta_inputs as $match ) {
1140 if (
1141 // Find exact match
1142 $existing->getMetaKey() === $match ||
1143 // Wildcard match - for input names like subtitles_*
1144 stripos( $match, '_*' ) === strlen( $match ) - 2 && stripos( $existing->getMetaKey(), str_replace( '_*', '', $match ) ) === 0
1145 ) {
1146 $should_skip = false;
1147 }
1148 }
1149
1150 if ( 'chapters' === $existing->getMetaKey() && ! empty( $video_data['chapters'] ) ) {
1151 $should_skip = true;
1152 }
1153
1154 if ( $should_skip ) {
1155 continue;
1156 }
1157
1158 $found = false;
1159 foreach ($meta_data as $meta_record) {
1160 if( !empty($meta_record['meta_value']) && $meta_record['meta_key'] == $existing->getMetaKey() ) {
1161 $found = true;
1162 break;
1163 }
1164 }
1165 if( !$found ) {
1166 $existing->delete();
1167 } else {
1168 $existing_meta_ids[$existing->getId()] = true;
1169 }
1170 }
1171
1172 }
1173
1174 // check for any meta data
1175 if (is_array($meta_data) && count($meta_data)) {
1176
1177 // we have meta, let's insert that
1178 foreach ($meta_data as $meta_record) {
1179 // it's possible that we switched the checkbox off and then on, by that time its id won't exist anymore! Todo: remove data-id instead?
1180 if( !empty($meta_record['id']) && empty($existing_meta_ids[$meta_record['id']]) ) {
1181 unset($meta_record['id']);
1182 }
1183
1184 // if the meta value has no ID associated, we replace the first one which exists, effectively preventing multiple values under the same meta key, which is something to improve, perhaps
1185 if( empty($meta_record['id']) ) {
1186 foreach( $existing_meta AS $existing ) {
1187 if( $meta_record['meta_key'] == $existing->getMetaKey() ) {
1188 $meta_record['id'] = $existing->getId();
1189 break;
1190 }
1191 }
1192 }
1193
1194 // add our video ID
1195 $meta_record['id_video'] = $this->id;
1196
1197 // create new record in DB
1198 $meta_object = new FV_Player_Db_Video_Meta(null, $meta_record, self::$DB_Instance);
1199
1200 // add meta data ID
1201 if( !empty($meta_record['id']) ) {
1202 $meta_object->link2db($meta_record['id']);
1203 } else if( empty($meta_record['meta_value']) ) {
1204 continue;
1205 }
1206
1207 $meta_object->save();
1208 $this->meta_data = $meta_object;
1209 }
1210 }
1211
1212 // add this meta into cache
1213 $cache = self::$DB_Instance->getVideosCache();
1214 $cache[$this->id] = $this;
1215 self::$DB_Instance->setVideosCache($cache);
1216
1217 $saved_attachments = $wpdb->get_col(
1218 $wpdb->prepare( "SELECT post_id FROM `{$wpdb->postmeta}` WHERE meta_key = 'fv_player_video_id' AND meta_value = %d", $this->getId() )
1219 );
1220
1221 // check for unused attachments
1222 if( !empty( $saved_attachments ) ) {
1223 foreach( $saved_attachments as $post_id ) {
1224 // remove if not used
1225 if( $splash_attachment_id != $post_id ) {
1226 delete_post_meta( $post_id, 'fv_player_video_id' );
1227 }
1228 }
1229 }
1230
1231 // store video id for splash attachment
1232 if( $splash_attachment_id ) {
1233 update_post_meta( $splash_attachment_id, 'fv_player_video_id', $this->getId() );
1234 }
1235
1236 return $this->id;
1237 } else {
1238 /*var_export($wpdb->last_error);
1239 var_export($wpdb->last_query);*/
1240 return false;
1241 }
1242 }
1243
1244 function save_do( $is_update ) {
1245 global $wpdb;
1246
1247 /*
1248 * Prepare SQL
1249 */
1250 $data_values = array();
1251
1252 foreach (get_object_vars($this) as $property => $value) {
1253 if ($property != 'id' && $property != 'is_valid' && $property != 'db_table_name' && $property != 'DB_Instance' && $property != 'meta_data') {
1254
1255 if ( 'live' === $property ) {
1256 $value = $value ? true : false;
1257 }
1258
1259 /**
1260 * Avoid issues if the import JSON sets a null value for what's expected to be string "toggle_advanced_settings":null
1261 */
1262 if ( is_string( $value ) ) {
1263 $value = wp_strip_all_tags( $value );
1264 }
1265
1266 if ( in_array( $property, array( 'height', 'live', 'splash_attachment_id', 'width' ) ) ) {
1267 $format[ $property ] = '%d';
1268
1269 if ( ! $value ) {
1270 $value = 0;
1271 }
1272
1273 } else if ( in_array( $property, array( 'duration' ) ) ) {
1274 $format[ $property ] = '%f';
1275
1276 if ( ! $value ) {
1277 $value = 0;
1278 }
1279
1280 } else {
1281 $format[ $property ] = '%s';
1282
1283 if ( ! $value ) {
1284 $value = '';
1285 }
1286 }
1287
1288 $data_values[ $property ] = $value;
1289 }
1290 }
1291
1292 if ($is_update) {
1293 $wpdb->update( self::$db_table_name, $data_values, array( 'id' => $this->id ), $format );
1294
1295 } else {
1296 $wpdb->insert( self::$db_table_name, $data_values, $format );
1297 }
1298
1299
1300 if (!$is_update) {
1301 $this->id = $wpdb->insert_id;
1302 }
1303 }
1304
1305 /**
1306 * Prepares this class' properties for export
1307 * and returns them in an associative array.
1308 *
1309 * @return array Returns an associative array of this class' properties and their values.
1310 */
1311 public function export() {
1312 $export_data = array();
1313 foreach (get_object_vars($this) as $property => $value) {
1314 if ($property != 'id' && $property != 'is_valid' && $property != 'db_table_name' && $property != 'DB_Instance' && $property != 'meta_data') {
1315 $export_data[$property] = $value;
1316 }
1317 }
1318
1319 return $export_data;
1320 }
1321
1322 /**
1323 * Removes video instance from the database.
1324 *
1325 * @return bool Returns true if the delete was successful, false otherwise.
1326 */
1327 public function delete() {
1328 // not a DB video? no delete
1329 if (!$this->is_valid) {
1330 return false;
1331 }
1332
1333 global $wpdb;
1334
1335 $wpdb->delete(self::$db_table_name, array('id' => $this->id));
1336
1337 if (!$wpdb->last_error) {
1338 // remove this meta from cache
1339 $cache = self::$DB_Instance->getVideosCache();
1340 if (isset($cache[$this->id])) {
1341 unset($cache[$this->id]);
1342 self::$DB_Instance->setVideosCache($cache);
1343 }
1344
1345 return true;
1346 } else {
1347 /*var_export($wpdb->last_error);
1348 var_export($wpdb->last_query);*/
1349 return false;
1350 }
1351 }
1352
1353 /**
1354 * Updates or instert a video meta row
1355 *
1356 * @param string $key The meta key
1357 * @param string $value Option meta value to remove
1358 *
1359 * @throws Exception When the underlying Meta object throws.
1360 *
1361 * @return int Returns number of removed meta rows
1362 *
1363 */
1364 public function deleteMetaValue( $key, $value = false ) {
1365 $deleted = 0;
1366 $data = $this->getMetaData();
1367
1368 if( count($data) ) {
1369 foreach( $data as $meta_object ) {
1370 if(
1371 $meta_object->getMetaKey() == $key &&
1372 ( !$value || $meta_object->getMetaValue() == $value )
1373 ) {
1374 if( $meta_object->delete() ) {
1375 $deleted++;
1376 }
1377 }
1378 }
1379 }
1380
1381 return $deleted;
1382 }
1383 }
1384