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 / controller / track.php

track.php in FV Player 8 trunk, at controller/track.php

362 lines 12.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /* This file doesn't load WordPress, it simple increment counters for posts in wp-content/cache/fv-tracker/{tag}-{site id}.data */
3
4 if( !defined('SHORTINIT') ) {
5 define('SHORTINIT',true);
6 }
7
8 // include wp-load.php
9 if( file_exists('../../../../wp-load.php') ) {
10 require('../../../../wp-load.php');
11 }
12
13 //require_once( ABSPATH . WPINC . '/pluggable.php' );
14
15 /**
16 * Including what's necessary for nonce verification
17 */
18 require_once( ABSPATH . WPINC . '/capabilities.php' );
19 require_once( ABSPATH . WPINC . '/class-wp-roles.php' );
20 require_once( ABSPATH . WPINC . '/class-wp-role.php' );
21 require_once( ABSPATH . WPINC . '/class-wp-user.php' );
22 require_once( ABSPATH . WPINC . '/user.php' );
23
24 // Translation and localization.
25 require_once( ABSPATH . WPINC . '/pomo/mo.php' );
26 require_once( ABSPATH . WPINC . '/l10n.php' );
27
28 if ( file_exists( ABSPATH . WPINC . '/class-wp-textdomain-registry.php' ) ) {
29 require_once( ABSPATH . WPINC . '/class-wp-textdomain-registry.php' );
30 }
31
32 require_once( ABSPATH . WPINC . '/class-wp-locale.php' );
33 require_once( ABSPATH . WPINC . '/class-wp-locale-switcher.php' );
34
35 if ( class_exists( 'WP_Textdomain_Registry' ) ) {
36 global $wp_textdomain_registry;
37 if ( ! $wp_textdomain_registry instanceof WP_Textdomain_Registry ) {
38 $wp_textdomain_registry = new WP_Textdomain_Registry();
39 }
40 }
41
42 require_once( ABSPATH . WPINC . '/pluggable.php' );
43 require_once( ABSPATH . WPINC . '/functions.php' );
44 require_once( ABSPATH . WPINC . '/formatting.php' );
45 require_once( ABSPATH . WPINC . '/link-template.php' );
46 require_once( ABSPATH . WPINC . '/shortcodes.php' );
47 require_once( ABSPATH . WPINC . '/general-template.php' );
48 require_once( ABSPATH . WPINC . '/class-wp-session-tokens.php' );
49 require_once( ABSPATH . WPINC . '/class-wp-user-meta-session-tokens.php' );
50 require_once( ABSPATH . WPINC . '/meta.php' );
51 require_once( ABSPATH . WPINC . '/kses.php' );
52 require_once( ABSPATH . WPINC . '/rest-api.php' );
53 require_once( ABSPATH . WPINC . '/blocks.php' );
54
55 // Without this plugins_url() won't work
56 wp_plugin_directory_constants();
57
58 // Without this the user login status won't work
59 wp_cookie_constants();
60
61 Class FvPlayerTrackerWorker {
62
63 private $wp_content = false;
64 private $cache_path = false;
65 private $cache_filename = false;
66 private $video_id = false;
67 private $post_id = false;
68 private $player_id = false;
69 private $user_id = 0;
70 private $guest_user_id = 0;
71 private $watched = false;
72 private $tag = false;
73
74 private $file = false;
75
76 function __construct() {
77
78 $blog_id = ! empty( $_REQUEST['blog_id'] ) ? intval( $_REQUEST['blog_id'] ) : false;
79 $tag = ! empty( $_REQUEST['tag'] ) ? sanitize_key( $_REQUEST['tag'] ) : false;
80 $video_id = ! empty( $_REQUEST['video_id'] ) ? intval( $_REQUEST['video_id'] ) : false;
81 $watched = ! empty( $_REQUEST['watched'] ) ? sanitize_text_field( urldecode( $_REQUEST['watched'] ) ) : false;
82
83 if(
84 ! $blog_id ||
85 ! $tag ||
86 ( ! $video_id && ! $watched && 'ping' !== $tag )
87 ) {
88 die( "Error: missing arguments!" );
89 }
90
91 // $action has one been added in WordPress 6.1 unfortunately
92 add_filter(
93 'nonce_life',
94 function( $seconds, $action = false ) {
95 if ( 'fv_player_track' === $action ) {
96 $seconds = 7 * DAY_IN_SECONDS;
97 }
98 return $seconds;
99 },
100 PHP_INT_MAX,
101 2
102 );
103
104 // Do not check HTTP auth as we did not load WP_Application_Passwords class
105 remove_filter( 'determine_current_user', 'wp_validate_application_password', 20 );
106
107 if ( empty( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'fv_player_track' ) ) {
108 die( "Error: invalid nonce!" );
109 }
110
111 if ( ! in_array( $tag, array( 'play', 'click', 'seconds', 'ping' ) ) ) {
112 die( "Error: invalid tag!" );
113 }
114
115 $this->tag = $tag;
116
117 $this->wp_content = dirname( dirname( dirname( dirname( __FILE__ ) ) ) );
118 $this->cache_path = $this->wp_content."/fv-player-tracking";
119 $this->cache_filename = "{$tag}-{$blog_id}.data";
120
121 $this->video_id = $video_id;
122 $this->player_id = !empty($_REQUEST['player_id']) ? intval($_REQUEST['player_id']) : false;
123 $this->post_id = !empty($_REQUEST['post_id']) ? intval($_REQUEST['post_id']) : false;
124 $this->user_id = intval($_REQUEST['user_id']);
125 $this->watched = $watched;
126
127 // TODO: Verify some kind of signature here
128
129 $this->checkCacheFile();
130 }
131
132 /**
133 * Check and initialize cache file
134 * @return void
135 */
136 function checkCacheFile() {
137 $full_path = $this->cache_path . "/" . $this->cache_filename;
138
139 //cache file exists?
140 if( file_exists( $full_path ) ) return;
141
142 //cache directory exists
143 if( !file_exists( $this->cache_path ) ){
144 //create dir
145 //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!
146 if( !mkdir( $this->cache_path, 0775, true ) ){
147 die("Error: failed to create cache directory.");
148 }
149 }
150
151 //init file
152 touch( $full_path );
153 }
154
155 /**
156 * Load cache file data, find specific video_id and increment coutner for it
157 * @return boolean True when file lock was obtained, this doesn't ensure successful write. Otherwise false is returned
158 */
159 function incrementCacheCounter() {
160 $max_attempts = 3;
161
162 for( $i = 0; $i < $max_attempts; $i++ ){
163
164 if( flock( $this->file, LOCK_EX | LOCK_NB ) ) {
165
166 //increment counter
167 $encoded_data = fgets( $this->file );
168 $data = false;
169 if( $encoded_data ) {
170 $data = json_decode( $encoded_data, true );
171
172 $json_error = json_last_error();
173 if( $json_error !== JSON_ERROR_NONE ) {
174 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents
175 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
176 ftruncate( $this->file, 0 );
177 return false;
178 }
179 }
180
181 if( !$data ) {
182 $data = array();
183 }
184
185 if ( 'seconds' === $this->tag ) {
186 $this->watched = json_decode( $this->watched, true );
187
188 $json_error = json_last_error();
189 if( $json_error !== JSON_ERROR_NONE ) {
190 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents
191 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
192 return false;
193 }
194
195 foreach ( $this->watched as $player_id => $players ) {
196
197 foreach( $players as $post_id => $videos ) {
198
199 foreach( $videos as $video_id => $seconds ) {
200
201 // Add to the existing JSON data
202 $found = false;
203 foreach( $data as $index => $item ) {
204 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 ) {
205 $data[$index]['seconds'] = round( $data[$index]['seconds'] + $seconds );
206 $found = true;
207 }
208 }
209
210 // New JSON data
211 if ( ! $found ) {
212 $data[] = array(
213 'video_id' => $video_id,
214 'post_id' => $post_id,
215 'player_id' => $player_id,
216 'user_id' => $this->user_id,
217 'guest_user_id' => $this->guest_user_id,
218 'seconds' => round($seconds)
219 );
220 }
221 }
222 }
223 }
224
225 } else if ( 'play' === $this->tag ) {
226 $found = false;
227 foreach( $data as $index => $item ) {
228 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 ) {
229 $data[$index]['play'] += 1;
230 $found = true;
231 break;
232 }
233 }
234
235 if( !$found ) {
236 $data[] = array(
237 'video_id' => $this->video_id,
238 'post_id' => $this->post_id,
239 'player_id' => $this->player_id,
240 'user_id' => $this->user_id,
241 'guest_user_id' => $this->guest_user_id,
242 'play' => 1
243 );
244 }
245 } else if ( 'click' === $this->tag ) {
246 $found = false;
247 foreach( $data as $index => $item ) {
248 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 ) {
249 $data[$index]['click'] += 1;
250 $found = true;
251 break;
252 }
253 }
254
255 if( !$found ) {
256 $data[] = array(
257 'video_id' => $this->video_id,
258 'post_id' => $this->post_id,
259 'player_id' => $this->player_id,
260 'user_id' => $this->user_id,
261 'guest_user_id' => $this->guest_user_id,
262 'click' => 1
263 );
264 }
265 }
266
267 $encoded_data = wp_json_encode($data);
268
269 ftruncate( $this->file, 0 );
270 rewind( $this->file );
271 fputs( $this->file, $encoded_data );
272
273 //UNLOCK
274 flock( $this->file, LOCK_UN );
275 return true;
276 }
277 else{
278 //wait random interval from 50ms to 100ms
279 usleep( wp_rand(50,100) );
280 }
281 }
282
283 return false;
284 }
285
286 /**
287 * Main tracker functionality
288 * @return void
289 */
290 function track() {
291
292 if ( empty( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'fv_player_track' ) ) {
293 die( "Error: invalid nonce!" );
294 }
295
296 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen
297 $this->file = fopen( $this->cache_path."/".$this->cache_filename, 'r+');
298
299 if ( ! $this->file ) {
300 die( "Error: failed to open cache file!" );
301 }
302
303 if ( 'ping' === $this->tag ) {
304 $max_attempts = 3;
305
306 for( $i = 0; $i < $max_attempts; $i++ ){
307 if ( flock( $this->file, LOCK_EX | LOCK_NB ) ) {
308 ftruncate( $this->file, 0 );
309 rewind( $this->file );
310 fputs( $this->file, wp_json_encode( array( 'pong' => time() ) ) );
311 flock( $this->file, LOCK_UN );
312
313 } else{
314 //wait random interval from 50ms to 100ms
315 usleep( wp_rand(50,100) );
316 }
317 }
318
319 } else {
320 $options = get_option('fvwpflowplayer');
321 $guest_user_id = 0;
322
323 if( absint( $_REQUEST['user_id'] ) == 0 && ! empty( $options['video_stats_enable_guest'] ) && 'true' === $options['video_stats_enable_guest']) { // guest user
324
325 if( isset( $_COOKIE['fv_player_stats_guest_user_id'] ) ) { // check if cookie is set
326 $guest_user_id = intval( $_COOKIE['fv_player_stats_guest_user_id'] );
327 } else { // create new guest user id
328 $last_guest_id = get_option( 'fv_player_stats_last_guest_user_id', 0 );
329 $last_guest_id = $last_guest_id + 1;
330
331 $guest_user_id = $last_guest_id;
332
333 update_option( 'fv_player_stats_last_guest_user_id', $last_guest_id );
334
335 // save cookie fo 1 year
336 setcookie( 'fv_player_stats_guest_user_id', $last_guest_id, time() + 60 * 60 * 24 * 365, '/' );
337 }
338 }
339
340 $this->guest_user_id = $guest_user_id;
341
342 if( ! $this->incrementCacheCounter() ) {
343 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents
344 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
345 }
346 }
347
348 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
349 fclose( $this->file );
350
351 // Add .htaccess to deny all direct access
352 $htaccess_path = $this->cache_path . '/.htaccess';
353 if ( ! file_exists( $htaccess_path ) ) {
354 file_put_contents( $htaccess_path, "# Deny access to tracking files\nOrder allow,deny\nDeny from all\n" );
355 }
356
357 }
358 }
359
360 $fv_player_tracker_worker = new FvPlayerTrackerWorker();
361 $fv_player_tracker_worker->track();
362