PluginProbe
FV Player 8 / 8.1
FV Player 8 v8.1
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 8.1, at models/db-video.php

1,372 lines 41.4 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(512) 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 $this->$key = FV_Player_Db::sanitize( $value );
453 }
454
455 // cache this video in DB object
456 if ($DB_Cache) {
457 $cache[$this->id] = $this;
458 }
459 } else {
460 // multiple IDs, create new video objects for each of them except the first one,
461 // for which we'll use this instance
462 $first_done = false;
463 foreach ($video_data as $db_record) {
464 if (!$first_done) {
465 // fill-in our internal variables
466 foreach ( $db_record as $key => $value ) {
467 // Do not set the caption if it's still in DB table.
468 if ( 'caption' === $key ) {
469 // Use it as the title if there is none.
470 if ( empty( $db_record->title ) ) {
471 $db_record->title = $value;
472 }
473 continue;
474 }
475 $this->$key = FV_Player_Db::sanitize( $value );
476 }
477
478 $first_done = true;
479
480 // cache this video in DB object
481 if ($DB_Cache) {
482 $cache[$this->id] = $this;
483 }
484 } else {
485 // create a new video object and populate it with DB values
486 $record_id = $db_record->id;
487 // if we don't unset this, we'll get warnings
488 unset($db_record->id);
489
490 if ($DB_Cache && !$DB_Cache->isVideoCached($record_id)) {
491 $video_object = new FV_Player_Db_Video( null, get_object_vars( $db_record ), self::$DB_Instance );
492 $video_object->link2db( $record_id );
493
494 // cache this video in DB object
495 if ( $DB_Cache ) {
496 $cache[ $record_id ] = $video_object;
497 }
498 }
499 }
500 }
501 }
502 $this->is_valid = true;
503 } else if ($all_cached) {
504 // fill the data for this class with data of the cached class
505 if ($multiID) {
506 $cached_video = $cache[reset($id)];
507 } else {
508 $cached_video = $cache[$id];
509 }
510
511 if ( $cached_video ) {
512 foreach ($cached_video->getAllDataValues() as $key => $value) {
513 $this->$key = FV_Player_Db::sanitize( $value );
514 }
515
516 // add meta data
517 $this->meta_data = $cached_video->getMetaData();
518
519 // make this class a valid video
520 $this->is_valid = true;
521 }
522 }
523 } else {
524 throw new Exception('No options nor a valid ID was provided for DB video instance.');
525 }
526
527 // update cache, if changed
528 if (isset($cache) && (!isset($all_cached) || !$all_cached)) {
529 self::$DB_Instance->setVideosCache($cache);
530 }
531 }
532
533 /**
534 * Makes this video linked to a record in database.
535 *
536 * This is used when loading multiple videos in the constructor,
537 * so we can return them as objects from the DB and any saving will
538 * not insert their duplicates.
539 *
540 * Also used when updating video via editor, so we keep certain fields.
541 *
542 * @param int $id The DB ID to which we'll link this video.
543 * @param bool $load_meta If true, the meta data will be loaded for the video from database.
544 * Used when loading multiple videos at once with the array $id constructor parameter.
545 *
546 * @throws Exception When the underlying Meta object throws.
547 */
548 public function link2db($id, $load_meta = false) {
549 $this->id = (int) $id;
550
551 // Some fields are not present in editor and need to load from DB
552 $fields = array(
553 'width',
554 'height',
555 'aspect_ratio',
556 'duration',
557 'last_check'
558 );
559
560 $missing_something = false;
561 foreach( $fields AS $field ) {
562 if( empty($this->$field) ) {
563 $missing_something = true;
564 break;
565 }
566 }
567
568 if( $missing_something ) {
569 $objVideo = new FV_Player_Db_Video( $id, array(), self::$DB_Instance );
570 foreach( $fields AS $field ) {
571 if( empty($this->$field) ) {
572 $this->$field = $objVideo->$field;
573 }
574 }
575 }
576
577 if ($load_meta) {
578 $this->meta_data = new FV_Player_Db_Video_Meta(null, array('id_video' => array($id)), self::$DB_Instance);
579 }
580 }
581
582 /**
583 * This method will manually link meta data to the video.
584 * Used when not using save() method to link meta data to video while saving it
585 * into the database (i.e. while previewing etc.)
586 *
587 * @param FV_Player_Db_Video_Meta $meta_data The meta data object to link to this video.
588 *
589 * @throws Exception When an underlying meta data object throws an exception.
590 */
591 public function link2meta($meta_data) {
592 if (is_array($meta_data) && count($meta_data)) {
593 // we have meta, let's insert that
594 $first_done = false;
595 foreach ($meta_data as $meta_record) {
596 // create new record in DB
597 $meta_object = new FV_Player_Db_Video_Meta(null, $meta_record, self::$DB_Instance);
598
599 // link to DB, if the meta record has an ID
600 if (!empty($meta_record['id'])) {
601 $meta_object->link2db($meta_record['id']);
602 }
603
604 if (!$first_done) {
605 $this->meta_data = array($meta_object);
606 $first_done = true;
607 } else {
608 $this->meta_data[] = $meta_object;
609 }
610 }
611 } else if ($meta_data === -1) {
612 $this->meta_data = -1;
613 }
614 }
615
616 /**
617 * Searches for a player video via custom query.
618 * Used in plugins such as HLS which will
619 * provide video src data but not ID to search for.
620 *
621 * @param bool $like The LIKE part for the database query. If false, exact match is used.
622 * @param null $fields Fields to return for this search. If null, all fields are returned.
623 *
624 * @return bool Returns true if any data were loaded, false otherwise.
625 */
626 public function searchBySrc($like = false, $fields = null) {
627 _deprecated_function( __FUNCTION__, '7.5.22', 'FV_Player_Db::query_videos' );
628
629 global $wpdb;
630
631 if ( $like ) {
632 $row = $wpdb->get_row(
633 $wpdb->prepare(
634 "SELECT * FROM `{$wpdb->prefix}fv_player_videos` WHERE src LIKE %s ORDER BY id DESC",
635 '%' . $wpdb->esc_like( $this->src ) . '%'
636 )
637 );
638
639 } else {
640 $row = $wpdb->get_row(
641 $wpdb->prepare( "SELECT * FROM `{$wpdb->prefix}fv_player_videos` WHERE src = %s ORDER BY id DESC", $this->src )
642 );
643 }
644
645 if (!$row) {
646 return false;
647 } else {
648 // load up all values for this video
649 foreach ($row as $key => $value) {
650 if (property_exists($this, $key)) {
651 $this->$key = FV_Player_Db::sanitize( $value );
652 }
653 }
654
655 return true;
656 }
657 }
658
659 function find_video_meta_inputs( &$video_meta_inputs, $input ) {
660 if ( is_array( $input ) ) {
661 if ( ! empty( $input['name'] ) ) {
662
663 if ( ! empty( $input['video_meta'] ) ) {
664 $video_meta_inputs[] = $input['name'];
665 if ( ! empty( $input['language'] ) ) {
666 $video_meta_inputs[] = $input['name'] . '_*';
667 }
668 }
669
670 if ( ! empty( $input['children'] ) ) {
671 $this->find_video_meta_inputs( $video_meta_inputs, $input['children'] );
672 }
673 return;
674 }
675
676 foreach ( $input as $v ) {
677 $this->find_video_meta_inputs( $video_meta_inputs, $v );
678 }
679 }
680 }
681
682 /**
683 * Returns all options data for this video.
684 *
685 * @return array Returns all options data for this video.
686 */
687 public function getAllDataValues() {
688 $data = array();
689 foreach (get_object_vars($this) as $property => $value) {
690 if ($property != 'is_valid' && $property != 'db_table_name' && $property != 'DB_Instance' && $property != 'meta_data') {
691 if ( 'live' === $property ) {
692 $value = $value ? true : false;
693 }
694
695 $data[$property] = $value;
696 }
697 }
698
699 return $data;
700 }
701
702 /**
703 * Returns meta data for this video.
704 *
705 * @return FV_Player_Db_Video_Meta[] Returns all meta data for this video.
706 * @throws Exception When an underlying meta data object throws an exception.
707 */
708 public function getMetaData() {
709 // meta data already loaded and present, return them
710 if ($this->meta_data && $this->meta_data !== -1) {
711 // meta data will be an array if we filled all of them at once
712 // from database at the time when player is initially created
713 if (is_array($this->meta_data)) {
714 return $this->meta_data;
715 } else if ( self::$DB_Instance->isVideoMetaCached($this->id) ) {
716 $cache = self::$DB_Instance->getVideoMetaCache();
717 return $cache[$this->id];
718 } else {
719 if ($this->meta_data && $this->meta_data->getIsValid()) {
720 return array( $this->meta_data );
721 } else {
722 return array();
723 }
724 }
725
726 // meta data not loaded yet - load them now if the player exists
727 } else if ($this->meta_data === null && $this->getId() ) {
728
729 $this->meta_data = new FV_Player_Db_Video_Meta(null, array('id_video' => array( $this->getId() ) ), self::$DB_Instance);
730
731 // set meta data to -1, so we know we didn't get any meta data for this video
732 if (!$this->meta_data->getIsValid()) {
733 $this->meta_data = -1;
734 return array();
735 } else {
736 if ($this->meta_data && $this->meta_data->getIsValid()) {
737 // meta data will be an array if we filled all of them at once
738 // from database at the time when player is initially created
739 if (is_array($this->meta_data)) {
740 return $this->meta_data;
741 } else if ( self::$DB_Instance->isVideoMetaCached($this->id) ) {
742 $cache = self::$DB_Instance->getVideoMetaCache();
743 return $cache[$this->id];
744 } else {
745 if ($this->meta_data && $this->meta_data->getIsValid()) {
746 return array( $this->meta_data );
747 } else {
748 return array();
749 }
750 }
751 } else {
752 return array();
753 }
754 }
755 } else {
756 return array();
757 }
758 }
759
760 /**
761 * Returns actual meta data for a key for this video.
762 */
763 public function getMetaValue( $key, $single = false ) {
764 $output = array();
765 $data = $this->getMetaData();
766 if (count($data)) {
767 foreach ($data as $meta_object) {
768 if ($meta_object->getMetaKey() == $key) {
769 if( $single ) return $meta_object->getMetaValue();
770 $output[] = $meta_object->getMetaValue();
771 }
772 }
773 }
774 if( $single ) return false;
775 return $output;
776 }
777
778 /**
779 * Updates or instert a video meta row
780 *
781 * @param string $key The meta key
782 * @param string $value The meta value
783 * @param int $id ID of the existing video meta row.
784 * If it's left empty only one $key is allowed for the $this->getId() video ID.
785 *
786 * @throws Exception When the underlying Meta object throws.
787 *
788 * @return bool|int Returns record ID if successful, false otherwise.
789 *
790 */
791 public function updateMetaValue( $key, $value, $id = false ) {
792 $to_update = false;
793 $data = $this->getMetaData();
794
795 if (count($data)) {
796 foreach ($data as $meta_object) {
797 // find the matching video meta row and if id is provided as well, match on that too
798 if( ( !$id || $id == $meta_object->getId() ) && $meta_object->getMetaKey() == $key) {
799 $to_update = $meta_object->getId();
800
801 // if there is no change, then do not run any update and instead return the row ID
802 if(
803 is_string($value) && strcmp($meta_object->getMetaValue(), $value) == 0 ||
804 !is_string($value) && $meta_object->getMetaValue() == $value
805 ) {
806 return $to_update;
807 }
808 }
809 }
810 }
811
812 // if matching row has been found or if it was not found and no row id is provided (insert)
813 if( $to_update || !$to_update && !$id ) {
814 $meta = new FV_Player_Db_Video_Meta( null, array( 'id_video' => $this->getId(), 'meta_key' => $key, 'meta_value' => $value ), self::$DB_Instance );
815 if( $to_update ) $meta->link2db($to_update);
816 return $meta->save();
817 }
818
819 return false;
820 }
821
822 /**
823 * Lets you alter any of the video properties and then call save()
824 *
825 * @param string $key The meta key
826 * @param string $value The meta value
827 */
828 public function set( $key, $value ) {
829 $this->$key = FV_Player_Db::sanitize( $value );
830 }
831
832 /**
833 * Stores new video instance or updates and existing one
834 * in the database.
835 *
836 * @param array $meta_data An optional array of key-value objects
837 * with possible meta data for this video.
838 * @param bool $skip_meta If true, meta data will not be processed. Used in
839 * fv_player_guttenberg_attributes_save().
840 * We need this as empty $meta_data would mean it should
841 * all be removed.
842 * @param bool $skip_video_meta_check If true, video meta check will be skipped.
843 *
844 * @return bool|int Returns record ID if successful, false otherwise.
845 * @throws Exception When the underlying metadata object throws.
846 */
847 public function save( $meta_data = array(), $skip_meta = false, $skip_video_meta_check = false ) {
848 global $wpdb;
849
850 $is_update = ($this->id ? true : false);
851
852 // Save new video early so that we can attach video meta to the new video ID
853 if ( ! $is_update ) {
854 $this->save_do( $is_update );
855
856 // Bail if we encounter an error
857 if ( $wpdb->last_error ) {
858 return false;
859 }
860 }
861
862 $video_url = $this->getSrc();
863
864 /*
865 * Check video duration, fetch splash screen and title
866 */
867 $last_video_meta_check = $this->getLastCheck();
868 $last_video_meta_check_src = $this->getMetaValue( 'last_video_meta_check_src', true );
869
870 // Look if the last_video_meta_check_src is just about to save
871 if ( ! $last_video_meta_check_src && ! empty( $meta_data ) ) {
872 foreach( $meta_data AS $meta ) {
873 if ( 'last_video_meta_check_src' === $meta['meta_key'] ) {
874 $last_video_meta_check_src = $meta['meta_value'];
875 break;
876 }
877 }
878 }
879
880 // Check video duration, or even splash image and title if it was not checked previously
881 if(
882 ! $skip_video_meta_check &&
883 (
884 !$last_video_meta_check ||
885 $last_video_meta_check + 900 < time() ||
886 strcasecmp( $video_url, $last_video_meta_check_src ) != 0
887 )
888 ) {
889
890 // Check if FV Player Pro can fetch the video splash, title and duration
891 $video_data = apply_filters('fv_player_meta_data', $video_url, false, $this);
892
893 // No information obtained, do a basic check
894 if( !is_array($video_data) ) {
895 $video_data = array();
896
897 // was only the file path provided?
898 $parsed = wp_parse_url($video_url);
899 if( count($parsed) == 1 && !empty($parsed['path']) ) {
900 // then user the WordPress home URL
901 $video_url = home_url($video_url);
902 // but remove the "path" if WordPress runs in a folder
903 $wordpress_home = wp_parse_url(home_url());
904 if( !empty($wordpress_home['path']) ) {
905 $video_url = str_replace( $wordpress_home['path'], '', $video_url );
906 }
907 }
908
909 // only run the actual check for real URLs
910 if( filter_var($video_url, FILTER_VALIDATE_URL) && ! defined('PHPUnitTestMode') ) {
911
912 global $FV_Player_Checker;
913 $check = $FV_Player_Checker->check_mimetype( array($video_url), false, true );
914 foreach( array(
915 'aspect_ratio',
916 'duration',
917 'error',
918 'height',
919 'is_audio',
920 'is_encrypted',
921 'author_thumbnail',
922 'author_name',
923 'author_url',
924 'is_live',
925 'width'
926 ) AS $key ) {
927 if( !empty($check[$key]) ) {
928 $video_data[$key] = $check[$key];
929 }
930 }
931 }
932 }
933
934 if( is_array($video_data) ) {
935 $video_data = wp_parse_args( $video_data,
936 array(
937 'error' => false,
938 'duration' => false,
939 'is_live' => false,
940 'is_audio' => false,
941 'width' => false,
942 'height' => false,
943 'aspect_ratio' => false,
944 'chapters' => false,
945 )
946 );
947
948 // TODO: process chapters and also error, is_live, is_audio
949 // TODO: check caption, splash, chapters, auto_splash, auto_caption and also error, is_live, is_audio
950
951 $this->last_check = current_time( 'mysql', true );
952
953 if( $video_data['error'] ) {
954 $this->updateMetaValue( 'error', $video_data['error'] );
955
956 $last_error_count = $this->getMetaValue( 'error_count', true );
957
958 if( empty($last_error_count) ) $last_error_count = 0;
959 $last_error_count++;
960
961 $this->updateMetaValue( 'error_count', $last_error_count );
962
963 } else {
964 $this->deleteMetaValue( 'error' );
965 $this->deleteMetaValue( 'error_count' );
966 }
967
968 if( $video_data['width'] ) {
969 $this->width = intval($video_data['width']);
970 } else {
971 $this->width = false;
972 }
973
974 if( $video_data['height'] ) {
975 $this->height = intval($video_data['height']);
976 } else {
977 $this->height = false;
978 }
979
980 if( $video_data['aspect_ratio'] ) {
981 $this->aspect_ratio = round( floatval($video_data['aspect_ratio']), 4 );
982 } else if( intval($this->width) > 0 && $this->height ) {
983 $this->aspect_ratio = round( $this->height/$this->width, 4 );
984 } else {
985 $this->aspect_ratio = false;
986 }
987
988 if( $video_data['duration'] ) {
989 $this->duration = $video_data['duration'];
990
991 // Remove the legacy value stored in video meta
992 $this->deleteMetaValue( 'duration' );
993 } else if ( empty( $this->duration ) ) {
994 $this->duration = 0;
995 }
996
997 if( $video_data['is_live'] ) {
998 $this->live = true;
999 } else {
1000 $this->live = false;
1001 }
1002
1003 // Remove the legacy value stored in video meta
1004 $this->deleteMetaValue( 'live' );
1005
1006 if( !empty($video_data['is_encrypted']) ) {
1007 $this->updateMetaValue( 'encrypted', true );
1008
1009 } else {
1010 $this->deleteMetaValue( 'encrypted' );
1011 }
1012
1013 if( !empty($video_data['author_thumbnail']) ) {
1014 $this->updateMetaValue( 'author_thumbnail', $video_data['author_thumbnail'] );
1015
1016 } else {
1017 $this->deleteMetaValue( 'author_thumbnail' );
1018 }
1019
1020 if( !empty($video_data['author_name']) ) {
1021 $this->updateMetaValue( 'author_name', $video_data['author_name'] );
1022
1023 } else {
1024 $this->deleteMetaValue( 'author_name' );
1025 }
1026
1027 if( !empty($video_data['author_url']) ) {
1028 $this->updateMetaValue( 'author_url', $video_data['author_url'] );
1029
1030 } else {
1031 $this->deleteMetaValue( 'author_url' );
1032 }
1033
1034 if( !empty($video_data['chapters']) && ! $this->getMetaValue( 'chapters', true ) ) {
1035 $this->updateMetaValue( 'chapters', $video_data['chapters'] );
1036 }
1037
1038 if( !empty($video_data['is_audio']) ) {
1039 $this->updateMetaValue( 'audio', true );
1040
1041 } else {
1042 $this->deleteMetaValue( 'audio' );
1043 }
1044
1045 if( !empty($video_data['name']) && (
1046 !$this->getTitle() || $this->getMetaValue( 'auto_caption', true )
1047 ) ) {
1048 $this->title = $video_data['name'];
1049
1050 $this->updateMetaValue( 'auto_caption', 1 );
1051 }
1052
1053 if( !empty($video_data['synopsis']) && !$this->getMetaValue( 'synopsis', true ) ) {
1054 $synopsis = $video_data['synopsis'];
1055
1056 $this->updateMetaValue( 'synopsis', $synopsis );
1057 }
1058
1059 if( !empty($video_data['thumbnail']) && (
1060 !$this->getSplash() || $this->getMetaValue( 'auto_splash', true )
1061 ) ) {
1062 $this->splash = $video_data['thumbnail'];
1063
1064 $this->updateMetaValue( 'auto_splash', 1 );
1065 }
1066
1067 if( !empty($video_data['splash_attachment_id']) ) {
1068 $this->splash_attachment_id = $video_data['splash_attachment_id'];
1069 }
1070 } else {
1071 $meta_data = array();
1072 }
1073
1074 if ( $last_video_meta_check_src !== $video_url ) {
1075 $this->updateMetaValue( 'last_video_meta_check_src', $video_url );
1076 }
1077
1078 }
1079
1080 /*
1081 * If the splash has changed make sure the old Media Library item is no longer linked (postmeta) to this video
1082 */
1083 $splash_attachment_id = $this->getSplashAttachmentId();
1084
1085 if( $is_update ) {
1086 // check if splash url changed
1087 if( !empty( $splash_attachment_id ) ) {
1088 $saved_splash = wp_get_attachment_image_url($splash_attachment_id, 'full', false);
1089 if( !empty( $saved_splash ) ) {
1090 $saved_parse = wp_parse_url( $saved_splash );
1091 $current_parse = $this->getSplash() ? wp_parse_url( $this->getSplash() ) : false;
1092
1093 // if splash removed or changed, delete splash attachment
1094 if( !$current_parse || (strcmp( $saved_parse['path'], $current_parse['path'] ) !== 0) ) {
1095 delete_post_meta( $splash_attachment_id, 'fv_player_video_id', $this->getId() );
1096 $this->splash_attachment_id = '';
1097 }
1098 }
1099 }
1100 }
1101
1102 $this->save_do( true );
1103
1104 if (!$wpdb->last_error) {
1105
1106 // Get all registered input names which belong to video meta
1107 $video_meta_inputs = array();
1108
1109 if ( ! function_exists( 'fv_player_editor_video_fields' ) ) {
1110 require_once( dirname( __FILE__ ) . '/../controller/editor.php' );
1111 }
1112
1113 foreach (
1114 array(
1115 fv_player_editor_video_fields(),
1116 fv_player_editor_subtitle_fields()
1117 ) as $fields
1118 ) {
1119 $this->find_video_meta_inputs( $video_meta_inputs, $fields );
1120 }
1121
1122 if ( ! $skip_meta ) {
1123
1124 // we check which meta values are no longer set and remove these
1125 $existing_meta = $is_update ? $this->getMetaData() : array();
1126 $existing_meta_ids = array();
1127 foreach( $existing_meta as $existing ) {
1128
1129 // video meta fields which do not have an input field should be kept
1130 $should_skip = true;
1131 foreach ( $video_meta_inputs as $match ) {
1132 if (
1133 // Find exact match
1134 $existing->getMetaKey() === $match ||
1135 // Wildcard match - for input names like subtitles_*
1136 stripos( $match, '_*' ) === strlen( $match ) - 2 && stripos( $existing->getMetaKey(), str_replace( '_*', '', $match ) ) === 0
1137 ) {
1138 $should_skip = false;
1139 }
1140 }
1141
1142 if ( $should_skip ) {
1143 continue;
1144 }
1145
1146 $found = false;
1147 foreach ($meta_data as $meta_record) {
1148 if( !empty($meta_record['meta_value']) && $meta_record['meta_key'] == $existing->getMetaKey() ) {
1149 $found = true;
1150 break;
1151 }
1152 }
1153 if( !$found ) {
1154 $existing->delete();
1155 } else {
1156 $existing_meta_ids[$existing->getId()] = true;
1157 }
1158 }
1159
1160 }
1161
1162 // check for any meta data
1163 if (is_array($meta_data) && count($meta_data)) {
1164
1165 // we have meta, let's insert that
1166 foreach ($meta_data as $meta_record) {
1167 // 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?
1168 if( !empty($meta_record['id']) && empty($existing_meta_ids[$meta_record['id']]) ) {
1169 unset($meta_record['id']);
1170 }
1171
1172 // 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
1173 if( empty($meta_record['id']) ) {
1174 foreach( $existing_meta AS $existing ) {
1175 if( $meta_record['meta_key'] == $existing->getMetaKey() ) {
1176 $meta_record['id'] = $existing->getId();
1177 break;
1178 }
1179 }
1180 }
1181
1182 // add our video ID
1183 $meta_record['id_video'] = $this->id;
1184
1185 // create new record in DB
1186 $meta_object = new FV_Player_Db_Video_Meta(null, $meta_record, self::$DB_Instance);
1187
1188 // add meta data ID
1189 if( !empty($meta_record['id']) ) {
1190 $meta_object->link2db($meta_record['id']);
1191 } else if( empty($meta_record['meta_value']) ) {
1192 continue;
1193 }
1194
1195 $meta_object->save();
1196 $this->meta_data = $meta_object;
1197 }
1198 }
1199
1200 // add this meta into cache
1201 $cache = self::$DB_Instance->getVideosCache();
1202 $cache[$this->id] = $this;
1203 self::$DB_Instance->setVideosCache($cache);
1204
1205 $saved_attachments = $wpdb->get_col(
1206 $wpdb->prepare( "SELECT post_id FROM `{$wpdb->postmeta}` WHERE meta_key = 'fv_player_video_id' AND meta_value = %d", $this->getId() )
1207 );
1208
1209 // check for unused attachments
1210 if( !empty( $saved_attachments ) ) {
1211 foreach( $saved_attachments as $post_id ) {
1212 // remove if not used
1213 if( $splash_attachment_id != $post_id ) {
1214 delete_post_meta( $post_id, 'fv_player_video_id' );
1215 }
1216 }
1217 }
1218
1219 // store video id for splash attachment
1220 if( $splash_attachment_id ) {
1221 update_post_meta( $splash_attachment_id, 'fv_player_video_id', $this->getId() );
1222 }
1223
1224 return $this->id;
1225 } else {
1226 /*var_export($wpdb->last_error);
1227 var_export($wpdb->last_query);*/
1228 return false;
1229 }
1230 }
1231
1232 function save_do( $is_update ) {
1233 global $wpdb;
1234
1235 /*
1236 * Prepare SQL
1237 */
1238 $data_values = array();
1239
1240 foreach (get_object_vars($this) as $property => $value) {
1241 if ($property != 'id' && $property != 'is_valid' && $property != 'db_table_name' && $property != 'DB_Instance' && $property != 'meta_data') {
1242
1243 if ( 'live' === $property ) {
1244 $value = $value ? true : false;
1245 }
1246
1247 /**
1248 * Avoid issues if the import JSON sets a null value for what's expected to be string "toggle_advanced_settings":null
1249 */
1250 if ( is_string( $value ) ) {
1251 $value = wp_strip_all_tags( $value );
1252 }
1253
1254 if ( in_array( $property, array( 'height', 'live', 'splash_attachment_id', 'width' ) ) ) {
1255 $format[ $property ] = '%d';
1256
1257 if ( ! $value ) {
1258 $value = 0;
1259 }
1260
1261 } else if ( in_array( $property, array( 'duration' ) ) ) {
1262 $format[ $property ] = '%f';
1263
1264 if ( ! $value ) {
1265 $value = 0;
1266 }
1267
1268 } else {
1269 $format[ $property ] = '%s';
1270
1271 if ( ! $value ) {
1272 $value = '';
1273 }
1274 }
1275
1276 $data_values[ $property ] = $value;
1277 }
1278 }
1279
1280 if ($is_update) {
1281 $wpdb->update( self::$db_table_name, $data_values, array( 'id' => $this->id ), $format );
1282
1283 } else {
1284 $wpdb->insert( self::$db_table_name, $data_values, $format );
1285 }
1286
1287
1288 if (!$is_update) {
1289 $this->id = $wpdb->insert_id;
1290 }
1291 }
1292
1293 /**
1294 * Prepares this class' properties for export
1295 * and returns them in an associative array.
1296 *
1297 * @return array Returns an associative array of this class' properties and their values.
1298 */
1299 public function export() {
1300 $export_data = array();
1301 foreach (get_object_vars($this) as $property => $value) {
1302 if ($property != 'id' && $property != 'is_valid' && $property != 'db_table_name' && $property != 'DB_Instance' && $property != 'meta_data') {
1303 $export_data[$property] = $value;
1304 }
1305 }
1306
1307 return $export_data;
1308 }
1309
1310 /**
1311 * Removes video instance from the database.
1312 *
1313 * @return bool Returns true if the delete was successful, false otherwise.
1314 */
1315 public function delete() {
1316 // not a DB video? no delete
1317 if (!$this->is_valid) {
1318 return false;
1319 }
1320
1321 global $wpdb;
1322
1323 $wpdb->delete(self::$db_table_name, array('id' => $this->id));
1324
1325 if (!$wpdb->last_error) {
1326 // remove this meta from cache
1327 $cache = self::$DB_Instance->getVideosCache();
1328 if (isset($cache[$this->id])) {
1329 unset($cache[$this->id]);
1330 self::$DB_Instance->setVideosCache($cache);
1331 }
1332
1333 return true;
1334 } else {
1335 /*var_export($wpdb->last_error);
1336 var_export($wpdb->last_query);*/
1337 return false;
1338 }
1339 }
1340
1341 /**
1342 * Updates or instert a video meta row
1343 *
1344 * @param string $key The meta key
1345 * @param string $value Option meta value to remove
1346 *
1347 * @throws Exception When the underlying Meta object throws.
1348 *
1349 * @return int Returns number of removed meta rows
1350 *
1351 */
1352 public function deleteMetaValue( $key, $value = false ) {
1353 $deleted = 0;
1354 $data = $this->getMetaData();
1355
1356 if( count($data) ) {
1357 foreach( $data as $meta_object ) {
1358 if(
1359 $meta_object->getMetaKey() == $key &&
1360 ( !$value || $meta_object->getMetaValue() == $value )
1361 ) {
1362 if( $meta_object->delete() ) {
1363 $deleted++;
1364 }
1365 }
1366 }
1367 }
1368
1369 return $deleted;
1370 }
1371 }
1372