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

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