tag = $tag; $this->wp_content = dirname( dirname( dirname( dirname( __FILE__ ) ) ) ); $this->cache_path = $this->wp_content."/fv-player-tracking"; $this->cache_filename = "{$tag}-{$blog_id}.data"; $this->video_id = $video_id; $this->player_id = !empty($_REQUEST['player_id']) ? intval($_REQUEST['player_id']) : false; $this->post_id = !empty($_REQUEST['post_id']) ? intval($_REQUEST['post_id']) : false; $this->user_id = intval($_REQUEST['user_id']); $this->watched = $watched; // TODO: Verify some kind of signature here $this->checkCacheFile(); } /** * Check and initialize cache file * @return void */ function checkCacheFile() { $full_path = $this->cache_path . "/" . $this->cache_filename; //cache file exists? if( file_exists( $full_path ) ) return; //cache directory exists if( !file_exists( $this->cache_path ) ){ //create dir //todo: actually don't create it, if it doesn't exist it should mean the option is not enabled and this script shouldn't write anything! if( !mkdir( $this->cache_path, 0775, true ) ){ die("Error: failed to create cache directory."); } } //init file touch( $full_path ); } /** * Load cache file data, find specific video_id and increment coutner for it * @return boolean True when file lock was obtained, this doesn't ensure successful write. Otherwise false is returned */ function incrementCacheCounter() { $max_attempts = 3; for( $i = 0; $i < $max_attempts; $i++ ){ if( flock( $this->file, LOCK_EX | LOCK_NB ) ) { //increment counter $encoded_data = fgets( $this->file ); $data = false; if( $encoded_data ) { $data = json_decode( $encoded_data, true ); $json_error = json_last_error(); if( $json_error !== JSON_ERROR_NONE ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents file_put_contents( $this->wp_content.'/fv-player-track-error.log', gmdate('r')." JSON decode error:\n".var_export( array( 'err' => $json_error, 'data' => $encoded_data ), true )."\n", FILE_APPEND ); // todo: remove ftruncate( $this->file, 0 ); return false; } } if( !$data ) { $data = array(); } if ( 'seconds' === $this->tag ) { $this->watched = json_decode( $this->watched, true ); $json_error = json_last_error(); if( $json_error !== JSON_ERROR_NONE ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents file_put_contents( $this->wp_content.'/fv-player-track-error.log', gmdate('r')." JSON decode error for watched:\n".var_export( array( 'err' => $json_error, 'data' => $this->watched ), true )."\n", FILE_APPEND ); // todo: remove return false; } foreach ( $this->watched as $player_id => $players ) { foreach( $players as $post_id => $videos ) { foreach( $videos as $video_id => $seconds ) { // Add to the existing JSON data $found = false; foreach( $data as $index => $item ) { if( $item['video_id'] == $video_id && $item['post_id'] == $post_id && $item['player_id'] == $player_id && $item['user_id'] == $this->user_id && $item['guest_user_id'] == $this->guest_user_id ) { $data[$index]['seconds'] = round( $data[$index]['seconds'] + $seconds ); $found = true; } } // New JSON data if ( ! $found ) { $data[] = array( 'video_id' => $video_id, 'post_id' => $post_id, 'player_id' => $player_id, 'user_id' => $this->user_id, 'guest_user_id' => $this->guest_user_id, 'seconds' => round($seconds) ); } } } } } else if ( 'play' === $this->tag ) { $found = false; foreach( $data as $index => $item ) { if( $item['video_id'] == $this->video_id && $item['post_id'] == $this->post_id && $item['player_id'] == $this->player_id && $item['user_id'] == $this->user_id && $item['guest_user_id'] == $this->guest_user_id ) { $data[$index]['play'] += 1; $found = true; break; } } if( !$found ) { $data[] = array( 'video_id' => $this->video_id, 'post_id' => $this->post_id, 'player_id' => $this->player_id, 'user_id' => $this->user_id, 'guest_user_id' => $this->guest_user_id, 'play' => 1 ); } } else if ( 'click' === $this->tag ) { $found = false; foreach( $data as $index => $item ) { if( $item['video_id'] == $this->video_id && $item['post_id'] == $this->post_id && $item['player_id'] == $this->player_id && $item['user_id'] == $this->user_id && $item['guest_user_id'] == $this->guest_user_id ) { $data[$index]['click'] += 1; $found = true; break; } } if( !$found ) { $data[] = array( 'video_id' => $this->video_id, 'post_id' => $this->post_id, 'player_id' => $this->player_id, 'user_id' => $this->user_id, 'guest_user_id' => $this->guest_user_id, 'click' => 1 ); } } $encoded_data = wp_json_encode($data); ftruncate( $this->file, 0 ); rewind( $this->file ); fputs( $this->file, $encoded_data ); //UNLOCK flock( $this->file, LOCK_UN ); return true; } else{ //wait random interval from 50ms to 100ms usleep( wp_rand(50,100) ); } } return false; } /** * Main tracker functionality * @return void */ function track() { if ( empty( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'fv_player_track' ) ) { die( "Error: invalid nonce!" ); } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen $this->file = fopen( $this->cache_path."/".$this->cache_filename, 'r+'); if ( ! $this->file ) { die( "Error: failed to open cache file!" ); } if ( 'ping' === $this->tag ) { $max_attempts = 3; for( $i = 0; $i < $max_attempts; $i++ ){ if ( flock( $this->file, LOCK_EX | LOCK_NB ) ) { ftruncate( $this->file, 0 ); rewind( $this->file ); fputs( $this->file, wp_json_encode( array( 'pong' => time() ) ) ); flock( $this->file, LOCK_UN ); } else{ //wait random interval from 50ms to 100ms usleep( wp_rand(50,100) ); } } } else { $options = get_option('fvwpflowplayer'); $guest_user_id = 0; if( absint( $_REQUEST['user_id'] ) == 0 && ! empty( $options['video_stats_enable_guest'] ) && 'true' === $options['video_stats_enable_guest']) { // guest user if( isset( $_COOKIE['fv_player_stats_guest_user_id'] ) ) { // check if cookie is set $guest_user_id = intval( $_COOKIE['fv_player_stats_guest_user_id'] ); } else { // create new guest user id $last_guest_id = get_option( 'fv_player_stats_last_guest_user_id', 0 ); $last_guest_id = $last_guest_id + 1; $guest_user_id = $last_guest_id; update_option( 'fv_player_stats_last_guest_user_id', $last_guest_id ); // save cookie fo 1 year setcookie( 'fv_player_stats_guest_user_id', $last_guest_id, time() + 60 * 60 * 24 * 365, '/' ); } } $this->guest_user_id = $guest_user_id; if( ! $this->incrementCacheCounter() ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents file_put_contents( $this->wp_content.'/fv-player-track-error.log', gmdate('r') . " flock or other error:\n".var_export( $this,true )."\n", FILE_APPEND ); // todo: remove } } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose fclose( $this->file ); // Add .htaccess to deny all direct access $htaccess_path = $this->cache_path . '/.htaccess'; if ( ! file_exists( $htaccess_path ) ) { file_put_contents( $htaccess_path, "# Deny access to tracking files\nOrder allow,deny\nDeny from all\n" ); } } } $fv_player_tracker_worker = new FvPlayerTrackerWorker(); $fv_player_tracker_worker->track();