Changeset 3484635
- Timestamp:
- 03/17/2026 10:04:29 AM (11 days ago)
- Location:
- presto-player
- Files:
-
- 20 edited
- 1 copied
-
tags/4.1.1 (copied) (copied from presto-player/trunk)
-
tags/4.1.1/dist/components/stats.json (modified) (1 diff)
-
tags/4.1.1/inc/Factory.php (modified) (2 diffs)
-
tags/4.1.1/inc/Integrations/LearnDash/LearnDash.php (modified) (1 diff)
-
tags/4.1.1/inc/Libraries/BunnyCDN.php (modified) (3 diffs)
-
tags/4.1.1/inc/Models/Model.php (modified) (50 diffs)
-
tags/4.1.1/inc/Services/VideoPostType.php (modified) (3 diffs)
-
tags/4.1.1/languages/presto-player.pot (modified) (6 diffs)
-
tags/4.1.1/presto-player.php (modified) (1 diff)
-
tags/4.1.1/readme.txt (modified) (2 diffs)
-
tags/4.1.1/vendor/composer/installed.php (modified) (2 diffs)
-
trunk/dist/components/stats.json (modified) (1 diff)
-
trunk/inc/Factory.php (modified) (2 diffs)
-
trunk/inc/Integrations/LearnDash/LearnDash.php (modified) (1 diff)
-
trunk/inc/Libraries/BunnyCDN.php (modified) (3 diffs)
-
trunk/inc/Models/Model.php (modified) (50 diffs)
-
trunk/inc/Services/VideoPostType.php (modified) (3 diffs)
-
trunk/languages/presto-player.pot (modified) (6 diffs)
-
trunk/presto-player.php (modified) (1 diff)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/vendor/composer/installed.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
presto-player/tags/4.1.1/dist/components/stats.json
r3468484 r3484635 1 1 { 2 "timestamp": "2026-0 2-24T09:46:04",2 "timestamp": "2026-03-17T10:02:49", 3 3 "compiler": { 4 4 "name": "node", 5 "version": "20.20. 0"5 "version": "20.20.1" 6 6 }, 7 7 "app": { -
presto-player/tags/4.1.1/inc/Factory.php
r3468484 r3484635 8 8 use PrestoPlayer\Support\Block; 9 9 use PrestoPlayer\Services\Scripts; 10 use PrestoPlayer\Services\BunnyCDN; 10 // Disabled: PrestoPlayer\Services\BunnyCDN class does not exist. Kept for reference. 11 // use PrestoPlayer\Services\BunnyCDN; 11 12 use PrestoPlayer\Services\Settings; 12 13 use PrestoPlayer\Services\AdminNotices; … … 36 37 public function getRules() { 37 38 return array( 38 BunnyCDN::class => self::SHARED, 39 // Disabled: BunnyCDN service class does not exist; this was a stale reference. 40 // BunnyCDN::class => self::SHARED, 39 41 Visits::class => self::SHARED, 40 42 ReusableVideos::class => self::SHARED, -
presto-player/tags/4.1.1/inc/Integrations/LearnDash/LearnDash.php
r3317175 r3484635 41 41 ob_start(); 42 42 ?> 43 data-video-cookie-key="<?php echo $data['cookieKey']; ?>"44 data-video-progression="<?php echo $data['videoProgress']; ?>"43 data-video-cookie-key="<?php echo esc_attr( $data['cookieKey'] ); ?>" 44 data-video-progression="<?php echo esc_attr( $data['videoProgress'] ); ?>" 45 45 data-video-provider="presto" 46 46 <?php -
presto-player/tags/4.1.1/inc/Libraries/BunnyCDN.php
r3122484 r3484635 1 <?php 1 <?php // phpcs:ignoreFile -- Third-party BunnyCDN SDK library; WPCS enforcement would require full rewrite. 2 2 3 3 namespace PrestoPlayer\Libraries; 4 4 5 /** 6 * Third-party BunnyCDN SDK library. 7 * 8 * NOTE: This file is NOT actively used — no code in either the free (Presto Player) 9 * or pro (Presto Player Pro) plugin instantiates or calls this class. 10 * It is kept here for future reference only. 11 * 12 * @see https://github.com/prestomade/presto-player/issues/898 13 * @see https://github.com/prestomade/presto-player/issues/899 14 */ 5 15 class BunnyCDN { 6 16 … … 1215 1225 $file = $api_call['data']; 1216 1226 1217 header( 'Content-type: application/octet-stream');1218 header( "Content-Disposition: attachment; filename=$file_name");1219 // will force to download...1227 $safe_name = str_replace( '"', '', sanitize_file_name( $file_name ) ); 1228 header( 'Content-Type: application/octet-stream' ); 1229 header( 'Content-Disposition: attachment; filename="' . $safe_name . '"' ); 1220 1230 echo $file; 1221 1231 } … … 1298 1308 1299 1309 public function DownloadFile( $file_url = '', $oupt_file_name = '' ) { 1300 // this is a fast way to download a file 1301 // remove any query string data 1302 if ( isset( $oupt_file_name ) ) { 1310 $parsed = wp_parse_url( $file_url ); 1311 if ( empty( $parsed['scheme'] ) || ! in_array( $parsed['scheme'], array( 'http', 'https' ), true ) ) { 1312 wp_die( 'Invalid file URL.', 'Error', array( 'response' => 400 ) ); 1313 } 1314 1315 if ( ! empty( $oupt_file_name ) ) { 1303 1316 $file_name = $oupt_file_name; 1304 } 1305 if ( empty( $oupt_file_name ) ) { 1317 } else { 1306 1318 $file_name = preg_replace( '/\?.*/', '', basename( $file_url ) ); 1319 } 1320 1321 $safe_name = str_replace( '"', '', sanitize_file_name( $file_name ) ); 1322 1323 $response = wp_safe_remote_get( $file_url, array( 'timeout' => 60 ) ); 1324 if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { 1325 wp_die( 'Download failed.', 'Error', array( 'response' => 500 ) ); 1307 1326 } 1308 1327 1309 1328 header( 'Content-Type: application/octet-stream' ); 1310 1329 header( 'Content-Transfer-Encoding: Binary' ); 1311 header( "Content-disposition: attachment; filename=$file_name" ); 1312 readfile( $file_url ); 1330 header( 'Content-Disposition: attachment; filename="' . $safe_name . '"' ); 1331 echo wp_remote_retrieve_body( $response ); 1332 exit; 1313 1333 } 1314 1334 1315 1335 1316 1336 public function DownloadFile1( $file_url ) { 1317 /* 1318 this is a slow way to download a file 1319 will allow you to download a remote file from any server that is accessible 1320 */ 1321 1322 $filename = $file_url; 1323 $filedata = @file_get_contents( $filename ); 1324 1325 // SUCCESS 1326 if ( $filedata ) { 1327 // GET A NAME FOR THE FILE 1328 // remove any query string data 1329 $basename = preg_replace( '/\?.*/', '', basename( $file_url ) ); 1330 // $basename = basename($filename); 1331 1332 // THESE HEADERS ARE USED ON ALL BROWSERS 1333 header( 'Content-Type: application-x/force-download' ); 1334 header( "Content-Disposition: attachment; filename=$basename" ); 1335 header( 'Content-length: ' . (string) ( strlen( $filedata ) ) ); 1336 header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', mktime( date( 'H' ) + 2, date( 'i' ), date( 's' ), date( 'm' ), date( 'd' ), date( 'Y' ) ) ) . ' GMT' ); 1337 header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' ); 1338 1339 // THIS HEADER MUST BE OMITTED FOR IE 6+ 1340 if ( false === strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE ' ) ) { 1341 header( 'Cache-Control: no-cache, must-revalidate' ); 1342 } 1343 1344 // THIS IS THE LAST HEADER 1345 header( 'Pragma: no-cache' ); 1346 1347 // FLUSH THE HEADERS TO THE BROWSER 1348 flush(); 1349 1350 // CAPTURE THE FILE IN THE OUTPUT BUFFERS - WILL BE FLUSHED AT SCRIPT END 1351 ob_start(); 1352 echo $filedata; 1353 } 1354 1355 // FAILURE 1356 else { 1357 die( "ERROR: UNABLE TO OPEN $filename" ); 1358 } 1337 $parsed = wp_parse_url( $file_url ); 1338 if ( empty( $parsed['scheme'] ) || ! in_array( $parsed['scheme'], array( 'http', 'https' ), true ) ) { 1339 wp_die( 'Invalid file URL.', 'Error', array( 'response' => 400 ) ); 1340 } 1341 1342 $response = wp_safe_remote_get( $file_url, array( 'timeout' => 60 ) ); 1343 if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { 1344 wp_die( 'Download failed.', 'Error', array( 'response' => 500 ) ); 1345 } 1346 1347 $filedata = wp_remote_retrieve_body( $response ); 1348 $basename = str_replace( '"', '', sanitize_file_name( preg_replace( '/\?.*/', '', basename( $file_url ) ) ) ); 1349 1350 header( 'Content-Type: application/octet-stream' ); 1351 header( 'Content-Disposition: attachment; filename="' . $basename . '"' ); 1352 header( 'Content-Length: ' . strlen( $filedata ) ); 1353 header( 'Cache-Control: no-cache, must-revalidate' ); 1354 header( 'Pragma: no-cache' ); 1355 1356 echo $filedata; 1357 exit; 1359 1358 } 1360 1359 -
presto-player/tags/4.1.1/inc/Models/Model.php
r3122484 r3484635 1 1 <?php 2 /** 3 * Base Model class for interfacing with custom database tables. 4 * 5 * @package PrestoPlayer\Models 6 */ 2 7 3 8 namespace PrestoPlayer\Models; … … 7 12 8 13 /** 9 * Model for interfacing with custom database tables 14 * Model for interfacing with custom database tables. 10 15 */ 11 16 abstract class Model implements ModelInterface { 12 17 13 18 /** 14 * Needs a table name 19 * Needs a table name. 15 20 * 16 21 * @var string … … 19 24 20 25 /** 21 * Store model attributes 26 * Store model attributes. 22 27 * 23 28 * @var object … … 26 31 27 32 /** 28 * Model schema 33 * Model schema. 29 34 * 30 35 * @return array … … 35 40 36 41 /** 37 * Guarded variables 42 * Guarded variables. 38 43 * 39 44 * @var array … … 42 47 43 48 /** 44 * Attributes we can query by 49 * Attributes we can query by. 45 50 * 46 51 * @var array … … 49 54 50 55 /** 51 * Optionally get something from the db 52 * 53 * @param integer $id 56 * Optionally get something from the db. 57 * 58 * @param integer $id Model ID. 54 59 */ 55 60 public function __construct( $id = 0 ) { 56 61 $this->attributes = new \stdClass(); 57 62 if ( ! empty( $id ) ) { 58 return $this->set( $this->get( $id )->toObject() ); 59 return $this; 60 } 61 return $this; 62 } 63 64 /** 65 * Get attributes properties 66 * 67 * @param string $property 63 $this->set( $this->get( $id )->toObject() ); 64 } 65 } 66 67 /** 68 * Get attributes properties. 69 * 70 * @param string $property Property name. 68 71 * @return mixed 69 72 */ … … 75 78 76 79 /** 77 * Get attributes properties 78 * 79 * @param string $property 80 * @return mixed 80 * Set attributes properties. 81 * 82 * @param string $property Property name. 83 * @param mixed $value Property value. 84 * @return void 81 85 */ 82 86 public function __set( $property, $value ) { … … 84 88 } 85 89 90 /** 91 * Get the table name. 92 * 93 * @return string 94 */ 86 95 public function getTableName() { 87 96 return $this->table; … … 89 98 90 99 /** 91 * Convert to Object 100 * Convert to Object. 92 101 * 93 102 * @return object … … 106 115 107 116 /** 108 * Convert to array 117 * Convert to array. 109 118 * 110 119 * @return array … … 115 124 116 125 /** 117 * Formats row data based on schema 118 * 119 * @param object $columns 126 * Formats row data based on schema. 127 * 128 * @param object $columns Row columns to format. 120 129 * @return object 121 130 */ … … 136 145 137 146 /** 138 * Fetch all models 139 * 140 * @return array Array of preset objects 147 * Fetch all models. 148 * 149 * @return array Array of preset objects. 141 150 */ 142 151 public function all() { 143 152 global $wpdb; 144 153 145 // maybe get only published if we have soft deletes154 // Maybe get only published if we have soft deletes. 146 155 $where = ! empty( $this->schema()['deleted_at'] ) ? "WHERE (deleted_at IS NULL OR deleted_at = '0000-00-00 00:00:00') " : ''; 147 156 148 157 $results = $wpdb->get_results( 149 "SELECT * FROM {$wpdb->prefix}{$this->table} $where" 158 "SELECT * FROM {$wpdb->prefix}{$this->table} $where" // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is a class property. $where is built from safe values. 150 159 ); 151 160 … … 154 163 155 164 /** 156 * Fetch models from db 157 * 158 * @param array $args 159 * @return Object Array of models with pagination data165 * Fetch models from db. 166 * 167 * @param array $args Query arguments. 168 * @return object|\WP_Error Array of models with pagination data. 160 169 */ 161 170 public function fetch( $args = array() ) { 162 171 global $wpdb; 163 172 164 // remove empties for querying173 // Remove empties for querying. 165 174 $args = array_filter( 166 175 wp_parse_args( … … 175 184 ); 176 185 177 // get query args186 // Get query args. 178 187 $query = array_filter( 179 188 $args, 180 189 function ( $key ) { 181 return in_array( $key, array( 'per_page', 'page', 'status', 'date_query', 'fields', 'order_by' ) );190 return in_array( $key, array( 'per_page', 'page', 'status', 'date_query', 'fields', 'order_by' ), true ); 182 191 }, 183 192 ARRAY_FILTER_USE_KEY … … 188 197 189 198 foreach ( $args as $attribute => $value ) { 190 // must be queryable and in schema191 if ( ! in_array( $attribute, $this->queryable ) || empty( $schema[ $attribute ] ) ) {199 // Must be queryable and in schema. 200 if ( ! in_array( $attribute, $this->queryable, true ) || empty( $schema[ $attribute ] ) ) { 192 201 unset( $args[ $attribute ] ); 193 202 continue; 194 203 } 195 204 196 // attribute schema205 // Attribute schema. 197 206 $attr_schema = $schema[ $attribute ]; 198 207 199 // force type208 // Force type. 200 209 settype( $value, $attr_schema['type'] ); 201 210 202 // sanitize input211 // Sanitize input. 203 212 if ( ! empty( $attr_schema['sanitize_callback'] ) ) { 204 213 $value = $attr_schema['sanitize_callback']( $value ); 205 214 } 206 215 207 // maybe add quotes 208 if ( in_array( $attr_schema['type'], array( 'integer', 'number', 'boolean' ) ) ) { 209 $where .= $wpdb->prepare( 'AND %1s=%2s ', $attribute, $value ); 216 // Column name is already validated against $this->queryable whitelist above. 217 if ( in_array( $attr_schema['type'], array( 'integer', 'number', 'boolean' ), true ) ) { 218 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $attribute is validated against queryable whitelist. 219 $where .= $wpdb->prepare( "AND `{$attribute}` = %d ", $value ); 210 220 } else { 211 $where .= $wpdb->prepare( "AND %1s='%2s' ", $attribute, $value ); 212 } 213 } 214 215 // soft deletes 221 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $attribute is validated against queryable whitelist. 222 $where .= $wpdb->prepare( "AND `{$attribute}` = %s ", $value ); 223 } 224 } 225 226 // Soft deletes. 216 227 if ( ! empty( $this->schema()['deleted_at'] ) ) { 217 228 $status = ! empty( $args['status'] ) ? $args['status'] : ''; … … 220 231 $where .= "AND (deleted_at IS NOT NULL OR deleted_at != '0000-00-00 00:00:00') "; 221 232 break; 222 default: // default to published233 default: // Default to published. 223 234 $where .= "AND (deleted_at IS NULL OR deleted_at = '0000-00-00 00:00:00') "; 224 235 break; … … 226 237 } 227 238 228 // before and after239 // Before and after date queries. 229 240 if ( ! empty( $query['date_query'] ) ) { 230 // use created at by default241 // Use created_at by default. 231 242 $query['date_query'] = wp_parse_args( 232 243 $query['date_query'], … … 236 247 ); 237 248 238 // check for field239 $field = ! empty( $this->schema()[ $query['date_query']['field'] ] ) ? sanitize_text_field( $query['date_query']['field'] ): null;249 // Check for valid field. 250 $field = ! empty( $this->schema()[ $query['date_query']['field'] ] ) ? $query['date_query']['field'] : null; 240 251 if ( ! $field ) { 241 252 return new \WP_Error( 'invalid_field', 'Cannot do a date query by ' . sanitize_text_field( $query['date_query']['field'] ) ); 242 253 } 243 254 244 // if after255 // Field name is validated against schema above. 245 256 if ( ! empty( $query['date_query']['after'] ) ) { 246 257 $where .= $wpdb->prepare( 247 "AND %1s >= '%2s' ", 248 sanitize_text_field( $field ), // i.e. created_at 249 date( 'Y-m-d H:i:s', strtotime( $query['date_query']['after'] ) ) // convert to date 258 "AND `{$field}` >= %s ", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $field is validated against schema whitelist. 259 gmdate( 'Y-m-d H:i:s', strtotime( $query['date_query']['after'] ) ) 250 260 ); 251 261 } 252 // before253 262 if ( ! empty( $query['date_query']['before'] ) ) { 254 263 $where .= $wpdb->prepare( 255 "AND %1s <= '%2s' ", 256 sanitize_text_field( $field ), // i.e. created_at 257 date( 'Y-m-d H:i:s', strtotime( $query['date_query']['before'] ) ) // convert to date 264 "AND `{$field}` <= %s ", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $field is validated against schema whitelist. 265 gmdate( 'Y-m-d H:i:s', strtotime( $query['date_query']['before'] ) ) 258 266 ); 259 267 } … … 262 270 $limit = (int) $query['per_page']; 263 271 $offset = (int) ( $query['per_page'] * ( $query['page'] - 1 ) ); 264 $pagination = $wpdb->prepare( 'LIMIT % 1s OFFSET %2s', $limit, $offset );272 $pagination = $wpdb->prepare( 'LIMIT %d OFFSET %d ', $limit, $offset ); 265 273 266 274 $select = '*'; … … 271 279 $order_by = ''; 272 280 if ( ! empty( $query['order_by'] ) ) { 273 $order_by .= 'ORDER BY'; 274 $number = count( $query['order_by'] ); 275 $i = 1; 281 $allowed_dirs = array( 'ASC', 'DESC' ); 282 $order_clauses = array(); 276 283 foreach ( $query['order_by'] as $attribute => $direction ) { 277 $order_by .= $wpdb->prepare( ' %1s %2s', $attribute, $direction ); 278 $order_by .= $i === $number ? '' : ','; 279 ++$i; 280 } 281 $order_by .= ' '; 282 } 283 284 $total = $wpdb->get_var( "SELECT count(id) as count FROM {$wpdb->prefix}{$this->table} $where$order_by" ); 284 if ( ! in_array( $attribute, $this->queryable, true ) && ! array_key_exists( $attribute, $schema ) ) { 285 continue; 286 } 287 $direction = in_array( strtoupper( $direction ), $allowed_dirs, true ) ? strtoupper( $direction ) : 'ASC'; 288 $order_clauses[] = "`{$attribute}` {$direction}"; 289 } 290 if ( ! empty( $order_clauses ) ) { 291 $order_by = 'ORDER BY ' . implode( ', ', $order_clauses ) . ' '; 292 } 293 } 294 295 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared -- $where, $order_by, $pagination are built from safe values above. 296 $total = $wpdb->get_var( "SELECT count(id) as count FROM {$wpdb->prefix}{$this->table} $where" ); 297 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared -- $select, $where, $order_by, $pagination are built from safe values above. 285 298 $results = $wpdb->get_results( "SELECT $select FROM {$wpdb->prefix}{$this->table} $where$order_by$pagination" ); 286 299 … … 294 307 295 308 /** 296 * Find a specific model based on query 309 * Find a specific model based on query. 310 * 311 * @param array $args Query arguments. 312 * @return Model|false 297 313 */ 298 314 public function findWhere( $args = array() ) { … … 303 319 304 320 /** 305 * Turns raw sql query results into models 306 * 307 * @param array $results 308 * @return array Array of Models 321 * Turns raw sql query results into models. 322 * 323 * @param array $results Raw database results. 324 * @return array Array of Models. 309 325 */ 310 326 protected function parseResults( $results ) { … … 317 333 318 334 $output = array(); 319 // return new models for each row335 // Return new models for each row. 320 336 foreach ( $results as $result ) { 321 337 $class = get_class( $this ); … … 326 342 } 327 343 344 /** 345 * Parse results into an array of IDs. 346 * 347 * @param array $results Raw database results. 348 * @return array Array of integer IDs. 349 */ 328 350 public function parseIds( $results ) { 329 351 if ( is_wp_error( $results ) ) { … … 342 364 343 365 /** 344 * Gets fresh data from the db 366 * Gets fresh data from the db. 345 367 * 346 368 * @return Model … … 354 376 355 377 /** 356 * Get default values set from sche am378 * Get default values set from schema. 357 379 * 358 380 * @return array … … 372 394 373 395 /** 374 * Unset guarded variables 375 * 376 * @param array $args 377 * @return void396 * Unset guarded variables. 397 * 398 * @param array $args Arguments to filter. 399 * @return array 378 400 */ 379 401 protected function unsetGuarded( $args = array() ) { 380 // unset guarded402 // Unset guarded. 381 403 foreach ( $this->guarded as $arg ) { 382 404 if ( $args[ $arg ] ) { … … 385 407 } 386 408 387 // we should never set an ID409 // We should never set an ID. 388 410 unset( $args['id'] ); 389 411 … … 392 414 393 415 /** 394 * Create a preset395 * 396 * @param array $args 416 * Create a model. 417 * 418 * @param array $args Attributes for the new model. 397 419 * @return integer 398 420 */ … … 400 422 global $wpdb; 401 423 402 // unset guarded args424 // Unset guarded args. 403 425 $args = $this->unsetGuarded( $args ); 404 426 405 // parse args with default args427 // Parse args with default args. 406 428 $args = wp_parse_args( $args, $this->getDefaults() ); 407 429 408 // creation time430 // Creation time. 409 431 if ( ! empty( $this->schema()['created_at'] ) ) { 410 432 $args['created_at'] = ! empty( $args['created_at'] ) ? $args['created_at'] : current_time( 'mysql' ); 411 433 } 412 434 413 // maybe serialize args435 // Maybe serialize args. 414 436 $args = $this->maybeSerializeArgs( $args ); 415 437 416 // insert438 // Insert. 417 439 $wpdb->insert( $wpdb->prefix . $this->table, $args ); 418 440 419 // set ID in attributes441 // Set ID in attributes. 420 442 $this->attributes->id = $wpdb->insert_id; 421 443 422 // created action444 // Created action. 423 445 do_action( "{$this->table}_created", $this ); 424 446 425 // return id447 // Return id. 426 448 return $this->attributes->id; 427 449 } 428 450 451 /** 452 * Maybe serialize array arguments. 453 * 454 * @param array $args Arguments to process. 455 * @return array 456 */ 429 457 protected function maybeSerializeArgs( $args ) { 430 458 foreach ( $args as $key => $arg ) { … … 438 466 } 439 467 468 /** 469 * Maybe unserialize array arguments. 470 * 471 * @param array $args Arguments to process. 472 * @return array 473 */ 440 474 protected function maybeUnSerializeArgs( $args ) { 441 475 foreach ( $args as $key => $arg ) { … … 456 490 * argument with the optional second array argument. 457 491 * 458 * @param array $search Model to search for 459 * @param array $create Attributes to create 492 * @param array $search Model to search for. 493 * @param array $create Attributes to create. 460 494 * @return Model|\WP_Error 461 495 */ … … 470 504 } 471 505 472 // already created506 // Already created. 473 507 if ( ! empty( $models->data[0] ) ) { 474 508 $this->set( $models->data[0]->toObject() ); … … 476 510 } 477 511 478 // merge and create512 // Merge and create. 479 513 $merged = array_merge( $search, $create ); 480 514 $this->create( $merged ); 481 515 482 // return fresh instance516 // Return fresh instance. 483 517 return $this->fresh(); 484 518 } 485 519 486 520 /** 487 * Create and get a model 488 * 489 * @param array $args 521 * Create and get a model. 522 * 523 * @param array $args Attributes for the new model. 490 524 * @return Model|\WP_Error 491 525 */ … … 505 539 * argument with the optional second array argument. 506 540 * 507 * @param array $search Model to search for 508 * @param array $ create Attributes to create541 * @param array $search Model to search for. 542 * @param array $update Attributes to update or create with. 509 543 * @return Model|\WP_Error 510 544 */ 511 545 public function getOrCreate( $search, $update = array() ) { 512 // look for model546 // Look for model. 513 547 $models = $this->fetch( $search ); 514 548 if ( is_wp_error( $models ) ) { … … 516 550 } 517 551 518 // already created, update it552 // Already created, return it. 519 553 if ( ! empty( $models->data[0] ) && ! empty( $update ) ) { 520 554 $this->set( $models->data[0]->toObject() ); … … 522 556 } 523 557 524 // merge and create558 // Merge and create. 525 559 $merged = array_merge( $search, $update ); 526 560 527 // unset query stuff561 // Unset query stuff. 528 562 if ( ! empty( $merged['date_query'] ) ) { 529 563 unset( $merged['date_query'] ); … … 532 566 $this->create( $merged ); 533 567 534 // return fresh instance568 // Return fresh instance. 535 569 return $this->fresh(); 536 570 } … … 543 577 * argument with the optional second array argument. 544 578 * 545 * @param array $search Model to search for 546 * @param array $ create Attributes to create579 * @param array $search Model to search for. 580 * @param array $update Attributes to update or create with. 547 581 * @return Model|\WP_Error 548 582 */ 549 583 public function updateOrCreate( $search, $update = array() ) { 550 // look for model584 // Look for model. 551 585 $models = $this->fetch( $search ); 552 586 if ( is_wp_error( $models ) ) { … … 554 588 } 555 589 556 // already created, update it590 // Already created, update it. 557 591 if ( ! empty( $models->data[0] ) && ! empty( $update ) ) { 558 592 $this->set( $models->data[0]->toObject() ); … … 561 595 } 562 596 563 // merge and create597 // Merge and create. 564 598 $merged = array_merge( $search, $update ); 565 599 566 // unset query stuff600 // Unset query stuff. 567 601 if ( ! empty( $merged['date_query'] ) ) { 568 602 unset( $merged['date_query'] ); … … 571 605 $this->create( $merged ); 572 606 573 // return fresh instance607 // Return fresh instance. 574 608 return $this->fresh(); 575 609 } 576 610 577 611 /** 578 * Gets a single model 579 * 580 * @param int $id 581 * 582 * @return Model Model object 612 * Gets a single model. 613 * 614 * @param int $id Model ID. 615 * 616 * @return Model Model object. 583 617 */ 584 618 public function get( $id ) { 585 619 global $wpdb; 586 620 587 // maybe cache results621 // Maybe cache results. 588 622 $results = $wpdb->get_row( 623 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is a class property, not user input. 589 624 $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}{$this->table} WHERE id=%d", $id ) 590 625 ); … … 607 642 608 643 /** 609 * Set attributes 610 * 611 * @param array $args644 * Set attributes. 645 * 646 * @param array|object $args Attributes to set. 612 647 * @return Model 613 648 */ 614 649 public function set( $args ) { 650 // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores -- Existing hook name, cannot change without breaking backwards compatibility. 615 651 $this->attributes = apply_filters( "presto_player/{$this->table}/data", $this->formatRow( $args ) ); 616 652 return $this; … … 618 654 619 655 /** 620 * Update a model 621 * 622 * @param array $args 656 * Update a model. 657 * 658 * @param array $args Attributes to update. 623 659 * @return Model 624 660 */ … … 626 662 global $wpdb; 627 663 628 // id is required664 // ID is required. 629 665 if ( empty( $this->attributes->id ) ) { 630 666 return new \WP_Error( 'missing_parameter', __( 'You must first create or save this model to update it.', 'presto-player' ) ); 631 667 } 632 668 633 // unset guarded args669 // Unset guarded args. 634 670 $args = $this->unsetGuarded( $args ); 635 671 636 // parse args with default args672 // Parse args with default args. 637 673 $args = wp_parse_args( $args, $this->getDefaults() ); 638 674 639 // update time675 // Update time. 640 676 if ( ! empty( $this->schema()['updated_at'] ) ) { 641 677 $args['updated_at'] = ! empty( $args['updated_at'] ) ? $args['updated_at'] : current_time( 'mysql' ); 642 678 } 643 679 644 // maybe serialize680 // Maybe serialize. 645 681 $args = $this->maybeSerializeArgs( $args ); 646 682 647 // make update683 // Make update. 648 684 $updated = $wpdb->update( $wpdb->prefix . $this->table, $args, array( 'id' => (int) $this->id ) ); 649 685 650 // check for failure686 // Check for failure. 651 687 if ( false === $updated ) { 652 688 return new \WP_Error( 'update_failure', __( 'There was an issue updating the model.', 'presto-player' ) ); 653 689 } 654 690 655 // set attributes in model691 // Set attributes in model. 656 692 $this->set( $this->get( $this->id )->toObject() ); 657 693 658 // created action694 // Updated action. 659 695 do_action( "{$this->table}_updated", $this ); 660 696 … … 663 699 664 700 /** 665 * Trash model 701 * Trash model. 666 702 * 667 703 * @return Model … … 672 708 673 709 /** 674 * Untrash model 710 * Untrash model. 675 711 * 676 712 * @return Model … … 681 717 682 718 /** 683 * Permanently delete model 684 * 685 * @return boolean Whether the model was deleted 719 * Permanently delete model. 720 * 721 * @param array $where Where clause for deletion. 722 * @return boolean Whether the model was deleted. 686 723 */ 687 724 public function delete( $where = array() ) { … … 694 731 695 732 /** 696 * Bulk delete by a list of ids 697 * 698 * @param array $ids 699 * @return void733 * Bulk delete by a list of ids. 734 * 735 * @param array $ids Array of IDs to delete. 736 * @return boolean Whether the records were deleted. 700 737 */ 701 738 public function bulkDelete( $ids = array() ) { 702 739 global $wpdb; 703 740 704 // convert to comman separated 705 $ids = implode( ',', array_map( 'absint', $ids ) ); 706 707 // delete in bulk 741 $ids = array_filter( array_map( 'absint', $ids ) ); 742 if ( empty( $ids ) ) { 743 return false; 744 } 745 746 $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); 708 747 return (bool) $wpdb->query( 709 $wpdb->prepare( "DELETE FROM {$wpdb->prefix}{$this->table} WHERE id IN( %1s)", $ids )748 $wpdb->prepare( "DELETE FROM {$wpdb->prefix}{$this->table} WHERE id IN($placeholders)", ...$ids ) // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Table name is a class property. $placeholders is generated from array_fill with %d. 710 749 ); 711 750 } 712 751 713 752 /** 714 * Has One Relationship 753 * Has One Relationship. 754 * 755 * @param string $classname Related class name. 756 * @param string $parent_field Parent field name. 757 * @return HasOneRelationship 715 758 */ 716 759 public function hasOne( $classname, $parent_field ) { -
presto-player/tags/4.1.1/inc/Services/VideoPostType.php
r3429192 r3484635 234 234 if ( is_array( $tags ) ) { 235 235 foreach ( $tags as $key => $tag ) { 236 $tags[ $key ] = '<a href="?post_type=pp_video_block&pp_video_tag=' . $tag->term_id . '">' . $tag->name . '</a>'; 236 // Escape tag name to prevent XSS, use esc_url and absint for defense-in-depth. 237 $tags[ $key ] = '<a href="' . esc_url( '?post_type=pp_video_block&pp_video_tag=' . absint( $tag->term_id ) ) . '">' . esc_html( $tag->name ) . '</a>'; 237 238 } 238 echo implode( ', ', $tags ); 239 // Each tag link is already individually escaped above. 240 echo wp_kses_post( implode( ', ', $tags ) ); 239 241 } 240 242 return ob_get_clean(); … … 461 463 } 462 464 463 $selected = isset( $_GET[ $taxonomy ] ) ? $_GET[ $taxonomy ]: '';465 $selected = isset( $_GET[ $taxonomy ] ) ? absint( $_GET[ $taxonomy ] ) : ''; 464 466 $info_taxonomy = get_taxonomy( $taxonomy ); 465 467 468 ob_start(); 466 469 wp_dropdown_categories( 467 470 array( … … 475 478 ) 476 479 ); 480 $dropdown = ob_get_clean(); 481 482 $allowed_html = array( 483 'select' => array( 484 'name' => true, 485 'id' => true, 486 'class' => true, 487 ), 488 'option' => array( 489 'class' => true, 490 'value' => true, 491 'selected' => true, 492 ), 493 ); 494 echo wp_kses( $dropdown, $allowed_html ); 477 495 } 478 496 -
presto-player/tags/4.1.1/languages/presto-player.pot
r3468484 r3484635 8 8 "Content-Type: text/plain; charset=UTF-8\n" 9 9 "Content-Transfer-Encoding: 8bit\n" 10 "POT-Creation-Date: 2026-0 2-24T09:45:48+00:00\n"10 "POT-Creation-Date: 2026-03-17T10:02:32+00:00\n" 11 11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 12 12 "X-Generator: WP-CLI 2.12.0\n" … … 2023 2023 2024 2024 #: admin/blocks/shared/presets/parts/MailchimpConfig.js:75 2025 #: Services/VideoPostType.php:3 892025 #: Services/VideoPostType.php:391 2026 2026 msgid "Tag" 2027 2027 msgstr "" … … 2472 2472 2473 2473 #: admin/settings/pages/General.js:193 2474 #: Services/VideoPostType.php:41 02474 #: Services/VideoPostType.php:412 2475 2475 msgid "Media Hub" 2476 2476 msgstr "" … … 3405 3405 3406 3406 #: Integrations/Elementor/ReusableVideoWidget.php:126 3407 #: Services/VideoPostType.php:4 083407 #: Services/VideoPostType.php:410 3408 3408 msgid "Edit Media" 3409 3409 msgstr "" … … 3589 3589 msgstr "" 3590 3590 3591 #: Models/Model.php:6 303591 #: Models/Model.php:666 3592 3592 msgid "You must first create or save this model to update it." 3593 3593 msgstr "" 3594 3594 3595 #: Models/Model.php:6 523595 #: Models/Model.php:688 3596 3596 msgid "There was an issue updating the model." 3597 3597 msgstr "" … … 4026 4026 msgstr "" 4027 4027 4028 #: Services/VideoPostType.php:32 24028 #: Services/VideoPostType.php:324 4029 4029 msgid "Enter a title..." 4030 4030 msgstr "" 4031 4031 4032 #: Services/VideoPostType.php:38 44032 #: Services/VideoPostType.php:386 4033 4033 msgctxt "post type general name" 4034 4034 msgid "Media Tags" 4035 4035 msgstr "" 4036 4036 4037 #: Services/VideoPostType.php:38 54037 #: Services/VideoPostType.php:387 4038 4038 msgctxt "post type singular name" 4039 4039 msgid "Media Tag" 4040 4040 msgstr "" 4041 4041 4042 #: Services/VideoPostType.php:38 64042 #: Services/VideoPostType.php:388 4043 4043 msgctxt "admin menu" 4044 4044 msgid "Search Media Tags" 4045 4045 msgstr "" 4046 4046 4047 #: Services/VideoPostType.php:38 74047 #: Services/VideoPostType.php:389 4048 4048 msgctxt "add new on admin bar" 4049 4049 msgid "Popular Media Tags" 4050 4050 msgstr "" 4051 4051 4052 #: Services/VideoPostType.php:40 14052 #: Services/VideoPostType.php:403 4053 4053 msgctxt "post type general name" 4054 4054 msgid "Media Hub" 4055 4055 msgstr "" 4056 4056 4057 #: Services/VideoPostType.php:40 24057 #: Services/VideoPostType.php:404 4058 4058 msgctxt "post type singular name" 4059 4059 msgid "Media" 4060 4060 msgstr "" 4061 4061 4062 #: Services/VideoPostType.php:40 34062 #: Services/VideoPostType.php:405 4063 4063 msgctxt "admin menu" 4064 4064 msgid "Media" 4065 4065 msgstr "" 4066 4066 4067 #: Services/VideoPostType.php:40 44067 #: Services/VideoPostType.php:406 4068 4068 msgctxt "add new on admin bar" 4069 4069 msgid "Presto Media" 4070 4070 msgstr "" 4071 4071 4072 #: Services/VideoPostType.php:40 54072 #: Services/VideoPostType.php:407 4073 4073 msgctxt "Media" 4074 4074 msgid "Add New" 4075 4075 msgstr "" 4076 4076 4077 #: Services/VideoPostType.php:40 64077 #: Services/VideoPostType.php:408 4078 4078 msgid "Add New Media" 4079 4079 msgstr "" 4080 4080 4081 #: Services/VideoPostType.php:40 74081 #: Services/VideoPostType.php:409 4082 4082 msgid "New Media" 4083 4083 msgstr "" 4084 4084 4085 #: Services/VideoPostType.php:4 094085 #: Services/VideoPostType.php:411 4086 4086 msgid "View Media" 4087 4087 msgstr "" 4088 4088 4089 #: Services/VideoPostType.php:41 14089 #: Services/VideoPostType.php:413 4090 4090 msgid "Search Media" 4091 4091 msgstr "" 4092 4092 4093 #: Services/VideoPostType.php:41 24093 #: Services/VideoPostType.php:414 4094 4094 msgid "No Media found." 4095 4095 msgstr "" 4096 4096 4097 #: Services/VideoPostType.php:41 34097 #: Services/VideoPostType.php:415 4098 4098 msgid "No Media found in Trash." 4099 4099 msgstr "" 4100 4100 4101 #: Services/VideoPostType.php:41 44101 #: Services/VideoPostType.php:416 4102 4102 msgid "Filter Media list" 4103 4103 msgstr "" 4104 4104 4105 #: Services/VideoPostType.php:41 54105 #: Services/VideoPostType.php:417 4106 4106 msgid "Media list navigation" 4107 4107 msgstr "" 4108 4108 4109 #: Services/VideoPostType.php:41 64109 #: Services/VideoPostType.php:418 4110 4110 msgid "Media list" 4111 4111 msgstr "" 4112 4112 4113 #: Services/VideoPostType.php:41 74113 #: Services/VideoPostType.php:419 4114 4114 msgid "Media published." 4115 4115 msgstr "" 4116 4116 4117 #: Services/VideoPostType.php:4 184117 #: Services/VideoPostType.php:420 4118 4118 msgid "Media published privately." 4119 4119 msgstr "" 4120 4120 4121 #: Services/VideoPostType.php:4 194121 #: Services/VideoPostType.php:421 4122 4122 msgid "Media reverted to draft." 4123 4123 msgstr "" 4124 4124 4125 #: Services/VideoPostType.php:42 04125 #: Services/VideoPostType.php:422 4126 4126 msgid "Media scheduled." 4127 4127 msgstr "" 4128 4128 4129 #: Services/VideoPostType.php:42 14129 #: Services/VideoPostType.php:423 4130 4130 msgid "Media updated." 4131 4131 msgstr "" 4132 4132 4133 #: Services/VideoPostType.php:4 684133 #: Services/VideoPostType.php:471 4134 4134 #, php-format 4135 4135 msgid "Show all %s" -
presto-player/tags/4.1.1/presto-player.php
r3468484 r3484635 4 4 * Plugin URI: http://prestoplayer.com 5 5 * Description: A beautiful, fast media player for WordPress. 6 * Version: 4.1. 06 * Version: 4.1.1 7 7 * Author: Presto Made, Inc 8 8 * Author URI: https://prestoplayer.com/ -
presto-player/tags/4.1.1/readme.txt
r3468484 r3484635 5 5 Requires at least: 6.3 6 6 Tested up to: 6.9 7 Stable tag: 4.1. 07 Stable tag: 4.1.1 8 8 Requires PHP: 7.3 9 9 License: GPLv2 or later … … 157 157 == Changelog == 158 158 159 = 4.1.1 = 160 * Security: Multiple security hardening improvements. 161 159 162 = 4.1.0 = 160 163 * New: Automatic caption generation with BunnyCDN. -
presto-player/tags/4.1.1/vendor/composer/installed.php
r3468484 r3484635 2 2 'root' => array( 3 3 'name' => 'course/player', 4 'pretty_version' => 'v4.1. 0',5 'version' => '4.1. 0.0',6 'reference' => ' 2bd337ed2bb551577b4496ecd06fed90574dc6d4',4 'pretty_version' => 'v4.1.1', 5 'version' => '4.1.1.0', 6 'reference' => '788f19da99c0e35e11897dd50e9a1aae24c68e63', 7 7 'type' => 'project', 8 8 'install_path' => __DIR__ . '/../../', … … 48 48 ), 49 49 'course/player' => array( 50 'pretty_version' => 'v4.1. 0',51 'version' => '4.1. 0.0',52 'reference' => ' 2bd337ed2bb551577b4496ecd06fed90574dc6d4',50 'pretty_version' => 'v4.1.1', 51 'version' => '4.1.1.0', 52 'reference' => '788f19da99c0e35e11897dd50e9a1aae24c68e63', 53 53 'type' => 'project', 54 54 'install_path' => __DIR__ . '/../../', -
presto-player/trunk/dist/components/stats.json
r3468484 r3484635 1 1 { 2 "timestamp": "2026-0 2-24T09:46:04",2 "timestamp": "2026-03-17T10:02:49", 3 3 "compiler": { 4 4 "name": "node", 5 "version": "20.20. 0"5 "version": "20.20.1" 6 6 }, 7 7 "app": { -
presto-player/trunk/inc/Factory.php
r3468484 r3484635 8 8 use PrestoPlayer\Support\Block; 9 9 use PrestoPlayer\Services\Scripts; 10 use PrestoPlayer\Services\BunnyCDN; 10 // Disabled: PrestoPlayer\Services\BunnyCDN class does not exist. Kept for reference. 11 // use PrestoPlayer\Services\BunnyCDN; 11 12 use PrestoPlayer\Services\Settings; 12 13 use PrestoPlayer\Services\AdminNotices; … … 36 37 public function getRules() { 37 38 return array( 38 BunnyCDN::class => self::SHARED, 39 // Disabled: BunnyCDN service class does not exist; this was a stale reference. 40 // BunnyCDN::class => self::SHARED, 39 41 Visits::class => self::SHARED, 40 42 ReusableVideos::class => self::SHARED, -
presto-player/trunk/inc/Integrations/LearnDash/LearnDash.php
r3317175 r3484635 41 41 ob_start(); 42 42 ?> 43 data-video-cookie-key="<?php echo $data['cookieKey']; ?>"44 data-video-progression="<?php echo $data['videoProgress']; ?>"43 data-video-cookie-key="<?php echo esc_attr( $data['cookieKey'] ); ?>" 44 data-video-progression="<?php echo esc_attr( $data['videoProgress'] ); ?>" 45 45 data-video-provider="presto" 46 46 <?php -
presto-player/trunk/inc/Libraries/BunnyCDN.php
r3122484 r3484635 1 <?php 1 <?php // phpcs:ignoreFile -- Third-party BunnyCDN SDK library; WPCS enforcement would require full rewrite. 2 2 3 3 namespace PrestoPlayer\Libraries; 4 4 5 /** 6 * Third-party BunnyCDN SDK library. 7 * 8 * NOTE: This file is NOT actively used — no code in either the free (Presto Player) 9 * or pro (Presto Player Pro) plugin instantiates or calls this class. 10 * It is kept here for future reference only. 11 * 12 * @see https://github.com/prestomade/presto-player/issues/898 13 * @see https://github.com/prestomade/presto-player/issues/899 14 */ 5 15 class BunnyCDN { 6 16 … … 1215 1225 $file = $api_call['data']; 1216 1226 1217 header( 'Content-type: application/octet-stream');1218 header( "Content-Disposition: attachment; filename=$file_name");1219 // will force to download...1227 $safe_name = str_replace( '"', '', sanitize_file_name( $file_name ) ); 1228 header( 'Content-Type: application/octet-stream' ); 1229 header( 'Content-Disposition: attachment; filename="' . $safe_name . '"' ); 1220 1230 echo $file; 1221 1231 } … … 1298 1308 1299 1309 public function DownloadFile( $file_url = '', $oupt_file_name = '' ) { 1300 // this is a fast way to download a file 1301 // remove any query string data 1302 if ( isset( $oupt_file_name ) ) { 1310 $parsed = wp_parse_url( $file_url ); 1311 if ( empty( $parsed['scheme'] ) || ! in_array( $parsed['scheme'], array( 'http', 'https' ), true ) ) { 1312 wp_die( 'Invalid file URL.', 'Error', array( 'response' => 400 ) ); 1313 } 1314 1315 if ( ! empty( $oupt_file_name ) ) { 1303 1316 $file_name = $oupt_file_name; 1304 } 1305 if ( empty( $oupt_file_name ) ) { 1317 } else { 1306 1318 $file_name = preg_replace( '/\?.*/', '', basename( $file_url ) ); 1319 } 1320 1321 $safe_name = str_replace( '"', '', sanitize_file_name( $file_name ) ); 1322 1323 $response = wp_safe_remote_get( $file_url, array( 'timeout' => 60 ) ); 1324 if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { 1325 wp_die( 'Download failed.', 'Error', array( 'response' => 500 ) ); 1307 1326 } 1308 1327 1309 1328 header( 'Content-Type: application/octet-stream' ); 1310 1329 header( 'Content-Transfer-Encoding: Binary' ); 1311 header( "Content-disposition: attachment; filename=$file_name" ); 1312 readfile( $file_url ); 1330 header( 'Content-Disposition: attachment; filename="' . $safe_name . '"' ); 1331 echo wp_remote_retrieve_body( $response ); 1332 exit; 1313 1333 } 1314 1334 1315 1335 1316 1336 public function DownloadFile1( $file_url ) { 1317 /* 1318 this is a slow way to download a file 1319 will allow you to download a remote file from any server that is accessible 1320 */ 1321 1322 $filename = $file_url; 1323 $filedata = @file_get_contents( $filename ); 1324 1325 // SUCCESS 1326 if ( $filedata ) { 1327 // GET A NAME FOR THE FILE 1328 // remove any query string data 1329 $basename = preg_replace( '/\?.*/', '', basename( $file_url ) ); 1330 // $basename = basename($filename); 1331 1332 // THESE HEADERS ARE USED ON ALL BROWSERS 1333 header( 'Content-Type: application-x/force-download' ); 1334 header( "Content-Disposition: attachment; filename=$basename" ); 1335 header( 'Content-length: ' . (string) ( strlen( $filedata ) ) ); 1336 header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', mktime( date( 'H' ) + 2, date( 'i' ), date( 's' ), date( 'm' ), date( 'd' ), date( 'Y' ) ) ) . ' GMT' ); 1337 header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' ); 1338 1339 // THIS HEADER MUST BE OMITTED FOR IE 6+ 1340 if ( false === strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE ' ) ) { 1341 header( 'Cache-Control: no-cache, must-revalidate' ); 1342 } 1343 1344 // THIS IS THE LAST HEADER 1345 header( 'Pragma: no-cache' ); 1346 1347 // FLUSH THE HEADERS TO THE BROWSER 1348 flush(); 1349 1350 // CAPTURE THE FILE IN THE OUTPUT BUFFERS - WILL BE FLUSHED AT SCRIPT END 1351 ob_start(); 1352 echo $filedata; 1353 } 1354 1355 // FAILURE 1356 else { 1357 die( "ERROR: UNABLE TO OPEN $filename" ); 1358 } 1337 $parsed = wp_parse_url( $file_url ); 1338 if ( empty( $parsed['scheme'] ) || ! in_array( $parsed['scheme'], array( 'http', 'https' ), true ) ) { 1339 wp_die( 'Invalid file URL.', 'Error', array( 'response' => 400 ) ); 1340 } 1341 1342 $response = wp_safe_remote_get( $file_url, array( 'timeout' => 60 ) ); 1343 if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { 1344 wp_die( 'Download failed.', 'Error', array( 'response' => 500 ) ); 1345 } 1346 1347 $filedata = wp_remote_retrieve_body( $response ); 1348 $basename = str_replace( '"', '', sanitize_file_name( preg_replace( '/\?.*/', '', basename( $file_url ) ) ) ); 1349 1350 header( 'Content-Type: application/octet-stream' ); 1351 header( 'Content-Disposition: attachment; filename="' . $basename . '"' ); 1352 header( 'Content-Length: ' . strlen( $filedata ) ); 1353 header( 'Cache-Control: no-cache, must-revalidate' ); 1354 header( 'Pragma: no-cache' ); 1355 1356 echo $filedata; 1357 exit; 1359 1358 } 1360 1359 -
presto-player/trunk/inc/Models/Model.php
r3122484 r3484635 1 1 <?php 2 /** 3 * Base Model class for interfacing with custom database tables. 4 * 5 * @package PrestoPlayer\Models 6 */ 2 7 3 8 namespace PrestoPlayer\Models; … … 7 12 8 13 /** 9 * Model for interfacing with custom database tables 14 * Model for interfacing with custom database tables. 10 15 */ 11 16 abstract class Model implements ModelInterface { 12 17 13 18 /** 14 * Needs a table name 19 * Needs a table name. 15 20 * 16 21 * @var string … … 19 24 20 25 /** 21 * Store model attributes 26 * Store model attributes. 22 27 * 23 28 * @var object … … 26 31 27 32 /** 28 * Model schema 33 * Model schema. 29 34 * 30 35 * @return array … … 35 40 36 41 /** 37 * Guarded variables 42 * Guarded variables. 38 43 * 39 44 * @var array … … 42 47 43 48 /** 44 * Attributes we can query by 49 * Attributes we can query by. 45 50 * 46 51 * @var array … … 49 54 50 55 /** 51 * Optionally get something from the db 52 * 53 * @param integer $id 56 * Optionally get something from the db. 57 * 58 * @param integer $id Model ID. 54 59 */ 55 60 public function __construct( $id = 0 ) { 56 61 $this->attributes = new \stdClass(); 57 62 if ( ! empty( $id ) ) { 58 return $this->set( $this->get( $id )->toObject() ); 59 return $this; 60 } 61 return $this; 62 } 63 64 /** 65 * Get attributes properties 66 * 67 * @param string $property 63 $this->set( $this->get( $id )->toObject() ); 64 } 65 } 66 67 /** 68 * Get attributes properties. 69 * 70 * @param string $property Property name. 68 71 * @return mixed 69 72 */ … … 75 78 76 79 /** 77 * Get attributes properties 78 * 79 * @param string $property 80 * @return mixed 80 * Set attributes properties. 81 * 82 * @param string $property Property name. 83 * @param mixed $value Property value. 84 * @return void 81 85 */ 82 86 public function __set( $property, $value ) { … … 84 88 } 85 89 90 /** 91 * Get the table name. 92 * 93 * @return string 94 */ 86 95 public function getTableName() { 87 96 return $this->table; … … 89 98 90 99 /** 91 * Convert to Object 100 * Convert to Object. 92 101 * 93 102 * @return object … … 106 115 107 116 /** 108 * Convert to array 117 * Convert to array. 109 118 * 110 119 * @return array … … 115 124 116 125 /** 117 * Formats row data based on schema 118 * 119 * @param object $columns 126 * Formats row data based on schema. 127 * 128 * @param object $columns Row columns to format. 120 129 * @return object 121 130 */ … … 136 145 137 146 /** 138 * Fetch all models 139 * 140 * @return array Array of preset objects 147 * Fetch all models. 148 * 149 * @return array Array of preset objects. 141 150 */ 142 151 public function all() { 143 152 global $wpdb; 144 153 145 // maybe get only published if we have soft deletes154 // Maybe get only published if we have soft deletes. 146 155 $where = ! empty( $this->schema()['deleted_at'] ) ? "WHERE (deleted_at IS NULL OR deleted_at = '0000-00-00 00:00:00') " : ''; 147 156 148 157 $results = $wpdb->get_results( 149 "SELECT * FROM {$wpdb->prefix}{$this->table} $where" 158 "SELECT * FROM {$wpdb->prefix}{$this->table} $where" // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is a class property. $where is built from safe values. 150 159 ); 151 160 … … 154 163 155 164 /** 156 * Fetch models from db 157 * 158 * @param array $args 159 * @return Object Array of models with pagination data165 * Fetch models from db. 166 * 167 * @param array $args Query arguments. 168 * @return object|\WP_Error Array of models with pagination data. 160 169 */ 161 170 public function fetch( $args = array() ) { 162 171 global $wpdb; 163 172 164 // remove empties for querying173 // Remove empties for querying. 165 174 $args = array_filter( 166 175 wp_parse_args( … … 175 184 ); 176 185 177 // get query args186 // Get query args. 178 187 $query = array_filter( 179 188 $args, 180 189 function ( $key ) { 181 return in_array( $key, array( 'per_page', 'page', 'status', 'date_query', 'fields', 'order_by' ) );190 return in_array( $key, array( 'per_page', 'page', 'status', 'date_query', 'fields', 'order_by' ), true ); 182 191 }, 183 192 ARRAY_FILTER_USE_KEY … … 188 197 189 198 foreach ( $args as $attribute => $value ) { 190 // must be queryable and in schema191 if ( ! in_array( $attribute, $this->queryable ) || empty( $schema[ $attribute ] ) ) {199 // Must be queryable and in schema. 200 if ( ! in_array( $attribute, $this->queryable, true ) || empty( $schema[ $attribute ] ) ) { 192 201 unset( $args[ $attribute ] ); 193 202 continue; 194 203 } 195 204 196 // attribute schema205 // Attribute schema. 197 206 $attr_schema = $schema[ $attribute ]; 198 207 199 // force type208 // Force type. 200 209 settype( $value, $attr_schema['type'] ); 201 210 202 // sanitize input211 // Sanitize input. 203 212 if ( ! empty( $attr_schema['sanitize_callback'] ) ) { 204 213 $value = $attr_schema['sanitize_callback']( $value ); 205 214 } 206 215 207 // maybe add quotes 208 if ( in_array( $attr_schema['type'], array( 'integer', 'number', 'boolean' ) ) ) { 209 $where .= $wpdb->prepare( 'AND %1s=%2s ', $attribute, $value ); 216 // Column name is already validated against $this->queryable whitelist above. 217 if ( in_array( $attr_schema['type'], array( 'integer', 'number', 'boolean' ), true ) ) { 218 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $attribute is validated against queryable whitelist. 219 $where .= $wpdb->prepare( "AND `{$attribute}` = %d ", $value ); 210 220 } else { 211 $where .= $wpdb->prepare( "AND %1s='%2s' ", $attribute, $value ); 212 } 213 } 214 215 // soft deletes 221 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $attribute is validated against queryable whitelist. 222 $where .= $wpdb->prepare( "AND `{$attribute}` = %s ", $value ); 223 } 224 } 225 226 // Soft deletes. 216 227 if ( ! empty( $this->schema()['deleted_at'] ) ) { 217 228 $status = ! empty( $args['status'] ) ? $args['status'] : ''; … … 220 231 $where .= "AND (deleted_at IS NOT NULL OR deleted_at != '0000-00-00 00:00:00') "; 221 232 break; 222 default: // default to published233 default: // Default to published. 223 234 $where .= "AND (deleted_at IS NULL OR deleted_at = '0000-00-00 00:00:00') "; 224 235 break; … … 226 237 } 227 238 228 // before and after239 // Before and after date queries. 229 240 if ( ! empty( $query['date_query'] ) ) { 230 // use created at by default241 // Use created_at by default. 231 242 $query['date_query'] = wp_parse_args( 232 243 $query['date_query'], … … 236 247 ); 237 248 238 // check for field239 $field = ! empty( $this->schema()[ $query['date_query']['field'] ] ) ? sanitize_text_field( $query['date_query']['field'] ): null;249 // Check for valid field. 250 $field = ! empty( $this->schema()[ $query['date_query']['field'] ] ) ? $query['date_query']['field'] : null; 240 251 if ( ! $field ) { 241 252 return new \WP_Error( 'invalid_field', 'Cannot do a date query by ' . sanitize_text_field( $query['date_query']['field'] ) ); 242 253 } 243 254 244 // if after255 // Field name is validated against schema above. 245 256 if ( ! empty( $query['date_query']['after'] ) ) { 246 257 $where .= $wpdb->prepare( 247 "AND %1s >= '%2s' ", 248 sanitize_text_field( $field ), // i.e. created_at 249 date( 'Y-m-d H:i:s', strtotime( $query['date_query']['after'] ) ) // convert to date 258 "AND `{$field}` >= %s ", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $field is validated against schema whitelist. 259 gmdate( 'Y-m-d H:i:s', strtotime( $query['date_query']['after'] ) ) 250 260 ); 251 261 } 252 // before253 262 if ( ! empty( $query['date_query']['before'] ) ) { 254 263 $where .= $wpdb->prepare( 255 "AND %1s <= '%2s' ", 256 sanitize_text_field( $field ), // i.e. created_at 257 date( 'Y-m-d H:i:s', strtotime( $query['date_query']['before'] ) ) // convert to date 264 "AND `{$field}` <= %s ", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $field is validated against schema whitelist. 265 gmdate( 'Y-m-d H:i:s', strtotime( $query['date_query']['before'] ) ) 258 266 ); 259 267 } … … 262 270 $limit = (int) $query['per_page']; 263 271 $offset = (int) ( $query['per_page'] * ( $query['page'] - 1 ) ); 264 $pagination = $wpdb->prepare( 'LIMIT % 1s OFFSET %2s', $limit, $offset );272 $pagination = $wpdb->prepare( 'LIMIT %d OFFSET %d ', $limit, $offset ); 265 273 266 274 $select = '*'; … … 271 279 $order_by = ''; 272 280 if ( ! empty( $query['order_by'] ) ) { 273 $order_by .= 'ORDER BY'; 274 $number = count( $query['order_by'] ); 275 $i = 1; 281 $allowed_dirs = array( 'ASC', 'DESC' ); 282 $order_clauses = array(); 276 283 foreach ( $query['order_by'] as $attribute => $direction ) { 277 $order_by .= $wpdb->prepare( ' %1s %2s', $attribute, $direction ); 278 $order_by .= $i === $number ? '' : ','; 279 ++$i; 280 } 281 $order_by .= ' '; 282 } 283 284 $total = $wpdb->get_var( "SELECT count(id) as count FROM {$wpdb->prefix}{$this->table} $where$order_by" ); 284 if ( ! in_array( $attribute, $this->queryable, true ) && ! array_key_exists( $attribute, $schema ) ) { 285 continue; 286 } 287 $direction = in_array( strtoupper( $direction ), $allowed_dirs, true ) ? strtoupper( $direction ) : 'ASC'; 288 $order_clauses[] = "`{$attribute}` {$direction}"; 289 } 290 if ( ! empty( $order_clauses ) ) { 291 $order_by = 'ORDER BY ' . implode( ', ', $order_clauses ) . ' '; 292 } 293 } 294 295 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared -- $where, $order_by, $pagination are built from safe values above. 296 $total = $wpdb->get_var( "SELECT count(id) as count FROM {$wpdb->prefix}{$this->table} $where" ); 297 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared -- $select, $where, $order_by, $pagination are built from safe values above. 285 298 $results = $wpdb->get_results( "SELECT $select FROM {$wpdb->prefix}{$this->table} $where$order_by$pagination" ); 286 299 … … 294 307 295 308 /** 296 * Find a specific model based on query 309 * Find a specific model based on query. 310 * 311 * @param array $args Query arguments. 312 * @return Model|false 297 313 */ 298 314 public function findWhere( $args = array() ) { … … 303 319 304 320 /** 305 * Turns raw sql query results into models 306 * 307 * @param array $results 308 * @return array Array of Models 321 * Turns raw sql query results into models. 322 * 323 * @param array $results Raw database results. 324 * @return array Array of Models. 309 325 */ 310 326 protected function parseResults( $results ) { … … 317 333 318 334 $output = array(); 319 // return new models for each row335 // Return new models for each row. 320 336 foreach ( $results as $result ) { 321 337 $class = get_class( $this ); … … 326 342 } 327 343 344 /** 345 * Parse results into an array of IDs. 346 * 347 * @param array $results Raw database results. 348 * @return array Array of integer IDs. 349 */ 328 350 public function parseIds( $results ) { 329 351 if ( is_wp_error( $results ) ) { … … 342 364 343 365 /** 344 * Gets fresh data from the db 366 * Gets fresh data from the db. 345 367 * 346 368 * @return Model … … 354 376 355 377 /** 356 * Get default values set from sche am378 * Get default values set from schema. 357 379 * 358 380 * @return array … … 372 394 373 395 /** 374 * Unset guarded variables 375 * 376 * @param array $args 377 * @return void396 * Unset guarded variables. 397 * 398 * @param array $args Arguments to filter. 399 * @return array 378 400 */ 379 401 protected function unsetGuarded( $args = array() ) { 380 // unset guarded402 // Unset guarded. 381 403 foreach ( $this->guarded as $arg ) { 382 404 if ( $args[ $arg ] ) { … … 385 407 } 386 408 387 // we should never set an ID409 // We should never set an ID. 388 410 unset( $args['id'] ); 389 411 … … 392 414 393 415 /** 394 * Create a preset395 * 396 * @param array $args 416 * Create a model. 417 * 418 * @param array $args Attributes for the new model. 397 419 * @return integer 398 420 */ … … 400 422 global $wpdb; 401 423 402 // unset guarded args424 // Unset guarded args. 403 425 $args = $this->unsetGuarded( $args ); 404 426 405 // parse args with default args427 // Parse args with default args. 406 428 $args = wp_parse_args( $args, $this->getDefaults() ); 407 429 408 // creation time430 // Creation time. 409 431 if ( ! empty( $this->schema()['created_at'] ) ) { 410 432 $args['created_at'] = ! empty( $args['created_at'] ) ? $args['created_at'] : current_time( 'mysql' ); 411 433 } 412 434 413 // maybe serialize args435 // Maybe serialize args. 414 436 $args = $this->maybeSerializeArgs( $args ); 415 437 416 // insert438 // Insert. 417 439 $wpdb->insert( $wpdb->prefix . $this->table, $args ); 418 440 419 // set ID in attributes441 // Set ID in attributes. 420 442 $this->attributes->id = $wpdb->insert_id; 421 443 422 // created action444 // Created action. 423 445 do_action( "{$this->table}_created", $this ); 424 446 425 // return id447 // Return id. 426 448 return $this->attributes->id; 427 449 } 428 450 451 /** 452 * Maybe serialize array arguments. 453 * 454 * @param array $args Arguments to process. 455 * @return array 456 */ 429 457 protected function maybeSerializeArgs( $args ) { 430 458 foreach ( $args as $key => $arg ) { … … 438 466 } 439 467 468 /** 469 * Maybe unserialize array arguments. 470 * 471 * @param array $args Arguments to process. 472 * @return array 473 */ 440 474 protected function maybeUnSerializeArgs( $args ) { 441 475 foreach ( $args as $key => $arg ) { … … 456 490 * argument with the optional second array argument. 457 491 * 458 * @param array $search Model to search for 459 * @param array $create Attributes to create 492 * @param array $search Model to search for. 493 * @param array $create Attributes to create. 460 494 * @return Model|\WP_Error 461 495 */ … … 470 504 } 471 505 472 // already created506 // Already created. 473 507 if ( ! empty( $models->data[0] ) ) { 474 508 $this->set( $models->data[0]->toObject() ); … … 476 510 } 477 511 478 // merge and create512 // Merge and create. 479 513 $merged = array_merge( $search, $create ); 480 514 $this->create( $merged ); 481 515 482 // return fresh instance516 // Return fresh instance. 483 517 return $this->fresh(); 484 518 } 485 519 486 520 /** 487 * Create and get a model 488 * 489 * @param array $args 521 * Create and get a model. 522 * 523 * @param array $args Attributes for the new model. 490 524 * @return Model|\WP_Error 491 525 */ … … 505 539 * argument with the optional second array argument. 506 540 * 507 * @param array $search Model to search for 508 * @param array $ create Attributes to create541 * @param array $search Model to search for. 542 * @param array $update Attributes to update or create with. 509 543 * @return Model|\WP_Error 510 544 */ 511 545 public function getOrCreate( $search, $update = array() ) { 512 // look for model546 // Look for model. 513 547 $models = $this->fetch( $search ); 514 548 if ( is_wp_error( $models ) ) { … … 516 550 } 517 551 518 // already created, update it552 // Already created, return it. 519 553 if ( ! empty( $models->data[0] ) && ! empty( $update ) ) { 520 554 $this->set( $models->data[0]->toObject() ); … … 522 556 } 523 557 524 // merge and create558 // Merge and create. 525 559 $merged = array_merge( $search, $update ); 526 560 527 // unset query stuff561 // Unset query stuff. 528 562 if ( ! empty( $merged['date_query'] ) ) { 529 563 unset( $merged['date_query'] ); … … 532 566 $this->create( $merged ); 533 567 534 // return fresh instance568 // Return fresh instance. 535 569 return $this->fresh(); 536 570 } … … 543 577 * argument with the optional second array argument. 544 578 * 545 * @param array $search Model to search for 546 * @param array $ create Attributes to create579 * @param array $search Model to search for. 580 * @param array $update Attributes to update or create with. 547 581 * @return Model|\WP_Error 548 582 */ 549 583 public function updateOrCreate( $search, $update = array() ) { 550 // look for model584 // Look for model. 551 585 $models = $this->fetch( $search ); 552 586 if ( is_wp_error( $models ) ) { … … 554 588 } 555 589 556 // already created, update it590 // Already created, update it. 557 591 if ( ! empty( $models->data[0] ) && ! empty( $update ) ) { 558 592 $this->set( $models->data[0]->toObject() ); … … 561 595 } 562 596 563 // merge and create597 // Merge and create. 564 598 $merged = array_merge( $search, $update ); 565 599 566 // unset query stuff600 // Unset query stuff. 567 601 if ( ! empty( $merged['date_query'] ) ) { 568 602 unset( $merged['date_query'] ); … … 571 605 $this->create( $merged ); 572 606 573 // return fresh instance607 // Return fresh instance. 574 608 return $this->fresh(); 575 609 } 576 610 577 611 /** 578 * Gets a single model 579 * 580 * @param int $id 581 * 582 * @return Model Model object 612 * Gets a single model. 613 * 614 * @param int $id Model ID. 615 * 616 * @return Model Model object. 583 617 */ 584 618 public function get( $id ) { 585 619 global $wpdb; 586 620 587 // maybe cache results621 // Maybe cache results. 588 622 $results = $wpdb->get_row( 623 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is a class property, not user input. 589 624 $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}{$this->table} WHERE id=%d", $id ) 590 625 ); … … 607 642 608 643 /** 609 * Set attributes 610 * 611 * @param array $args644 * Set attributes. 645 * 646 * @param array|object $args Attributes to set. 612 647 * @return Model 613 648 */ 614 649 public function set( $args ) { 650 // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores -- Existing hook name, cannot change without breaking backwards compatibility. 615 651 $this->attributes = apply_filters( "presto_player/{$this->table}/data", $this->formatRow( $args ) ); 616 652 return $this; … … 618 654 619 655 /** 620 * Update a model 621 * 622 * @param array $args 656 * Update a model. 657 * 658 * @param array $args Attributes to update. 623 659 * @return Model 624 660 */ … … 626 662 global $wpdb; 627 663 628 // id is required664 // ID is required. 629 665 if ( empty( $this->attributes->id ) ) { 630 666 return new \WP_Error( 'missing_parameter', __( 'You must first create or save this model to update it.', 'presto-player' ) ); 631 667 } 632 668 633 // unset guarded args669 // Unset guarded args. 634 670 $args = $this->unsetGuarded( $args ); 635 671 636 // parse args with default args672 // Parse args with default args. 637 673 $args = wp_parse_args( $args, $this->getDefaults() ); 638 674 639 // update time675 // Update time. 640 676 if ( ! empty( $this->schema()['updated_at'] ) ) { 641 677 $args['updated_at'] = ! empty( $args['updated_at'] ) ? $args['updated_at'] : current_time( 'mysql' ); 642 678 } 643 679 644 // maybe serialize680 // Maybe serialize. 645 681 $args = $this->maybeSerializeArgs( $args ); 646 682 647 // make update683 // Make update. 648 684 $updated = $wpdb->update( $wpdb->prefix . $this->table, $args, array( 'id' => (int) $this->id ) ); 649 685 650 // check for failure686 // Check for failure. 651 687 if ( false === $updated ) { 652 688 return new \WP_Error( 'update_failure', __( 'There was an issue updating the model.', 'presto-player' ) ); 653 689 } 654 690 655 // set attributes in model691 // Set attributes in model. 656 692 $this->set( $this->get( $this->id )->toObject() ); 657 693 658 // created action694 // Updated action. 659 695 do_action( "{$this->table}_updated", $this ); 660 696 … … 663 699 664 700 /** 665 * Trash model 701 * Trash model. 666 702 * 667 703 * @return Model … … 672 708 673 709 /** 674 * Untrash model 710 * Untrash model. 675 711 * 676 712 * @return Model … … 681 717 682 718 /** 683 * Permanently delete model 684 * 685 * @return boolean Whether the model was deleted 719 * Permanently delete model. 720 * 721 * @param array $where Where clause for deletion. 722 * @return boolean Whether the model was deleted. 686 723 */ 687 724 public function delete( $where = array() ) { … … 694 731 695 732 /** 696 * Bulk delete by a list of ids 697 * 698 * @param array $ids 699 * @return void733 * Bulk delete by a list of ids. 734 * 735 * @param array $ids Array of IDs to delete. 736 * @return boolean Whether the records were deleted. 700 737 */ 701 738 public function bulkDelete( $ids = array() ) { 702 739 global $wpdb; 703 740 704 // convert to comman separated 705 $ids = implode( ',', array_map( 'absint', $ids ) ); 706 707 // delete in bulk 741 $ids = array_filter( array_map( 'absint', $ids ) ); 742 if ( empty( $ids ) ) { 743 return false; 744 } 745 746 $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); 708 747 return (bool) $wpdb->query( 709 $wpdb->prepare( "DELETE FROM {$wpdb->prefix}{$this->table} WHERE id IN( %1s)", $ids )748 $wpdb->prepare( "DELETE FROM {$wpdb->prefix}{$this->table} WHERE id IN($placeholders)", ...$ids ) // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Table name is a class property. $placeholders is generated from array_fill with %d. 710 749 ); 711 750 } 712 751 713 752 /** 714 * Has One Relationship 753 * Has One Relationship. 754 * 755 * @param string $classname Related class name. 756 * @param string $parent_field Parent field name. 757 * @return HasOneRelationship 715 758 */ 716 759 public function hasOne( $classname, $parent_field ) { -
presto-player/trunk/inc/Services/VideoPostType.php
r3429192 r3484635 234 234 if ( is_array( $tags ) ) { 235 235 foreach ( $tags as $key => $tag ) { 236 $tags[ $key ] = '<a href="?post_type=pp_video_block&pp_video_tag=' . $tag->term_id . '">' . $tag->name . '</a>'; 236 // Escape tag name to prevent XSS, use esc_url and absint for defense-in-depth. 237 $tags[ $key ] = '<a href="' . esc_url( '?post_type=pp_video_block&pp_video_tag=' . absint( $tag->term_id ) ) . '">' . esc_html( $tag->name ) . '</a>'; 237 238 } 238 echo implode( ', ', $tags ); 239 // Each tag link is already individually escaped above. 240 echo wp_kses_post( implode( ', ', $tags ) ); 239 241 } 240 242 return ob_get_clean(); … … 461 463 } 462 464 463 $selected = isset( $_GET[ $taxonomy ] ) ? $_GET[ $taxonomy ]: '';465 $selected = isset( $_GET[ $taxonomy ] ) ? absint( $_GET[ $taxonomy ] ) : ''; 464 466 $info_taxonomy = get_taxonomy( $taxonomy ); 465 467 468 ob_start(); 466 469 wp_dropdown_categories( 467 470 array( … … 475 478 ) 476 479 ); 480 $dropdown = ob_get_clean(); 481 482 $allowed_html = array( 483 'select' => array( 484 'name' => true, 485 'id' => true, 486 'class' => true, 487 ), 488 'option' => array( 489 'class' => true, 490 'value' => true, 491 'selected' => true, 492 ), 493 ); 494 echo wp_kses( $dropdown, $allowed_html ); 477 495 } 478 496 -
presto-player/trunk/languages/presto-player.pot
r3468484 r3484635 8 8 "Content-Type: text/plain; charset=UTF-8\n" 9 9 "Content-Transfer-Encoding: 8bit\n" 10 "POT-Creation-Date: 2026-0 2-24T09:45:48+00:00\n"10 "POT-Creation-Date: 2026-03-17T10:02:32+00:00\n" 11 11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 12 12 "X-Generator: WP-CLI 2.12.0\n" … … 2023 2023 2024 2024 #: admin/blocks/shared/presets/parts/MailchimpConfig.js:75 2025 #: Services/VideoPostType.php:3 892025 #: Services/VideoPostType.php:391 2026 2026 msgid "Tag" 2027 2027 msgstr "" … … 2472 2472 2473 2473 #: admin/settings/pages/General.js:193 2474 #: Services/VideoPostType.php:41 02474 #: Services/VideoPostType.php:412 2475 2475 msgid "Media Hub" 2476 2476 msgstr "" … … 3405 3405 3406 3406 #: Integrations/Elementor/ReusableVideoWidget.php:126 3407 #: Services/VideoPostType.php:4 083407 #: Services/VideoPostType.php:410 3408 3408 msgid "Edit Media" 3409 3409 msgstr "" … … 3589 3589 msgstr "" 3590 3590 3591 #: Models/Model.php:6 303591 #: Models/Model.php:666 3592 3592 msgid "You must first create or save this model to update it." 3593 3593 msgstr "" 3594 3594 3595 #: Models/Model.php:6 523595 #: Models/Model.php:688 3596 3596 msgid "There was an issue updating the model." 3597 3597 msgstr "" … … 4026 4026 msgstr "" 4027 4027 4028 #: Services/VideoPostType.php:32 24028 #: Services/VideoPostType.php:324 4029 4029 msgid "Enter a title..." 4030 4030 msgstr "" 4031 4031 4032 #: Services/VideoPostType.php:38 44032 #: Services/VideoPostType.php:386 4033 4033 msgctxt "post type general name" 4034 4034 msgid "Media Tags" 4035 4035 msgstr "" 4036 4036 4037 #: Services/VideoPostType.php:38 54037 #: Services/VideoPostType.php:387 4038 4038 msgctxt "post type singular name" 4039 4039 msgid "Media Tag" 4040 4040 msgstr "" 4041 4041 4042 #: Services/VideoPostType.php:38 64042 #: Services/VideoPostType.php:388 4043 4043 msgctxt "admin menu" 4044 4044 msgid "Search Media Tags" 4045 4045 msgstr "" 4046 4046 4047 #: Services/VideoPostType.php:38 74047 #: Services/VideoPostType.php:389 4048 4048 msgctxt "add new on admin bar" 4049 4049 msgid "Popular Media Tags" 4050 4050 msgstr "" 4051 4051 4052 #: Services/VideoPostType.php:40 14052 #: Services/VideoPostType.php:403 4053 4053 msgctxt "post type general name" 4054 4054 msgid "Media Hub" 4055 4055 msgstr "" 4056 4056 4057 #: Services/VideoPostType.php:40 24057 #: Services/VideoPostType.php:404 4058 4058 msgctxt "post type singular name" 4059 4059 msgid "Media" 4060 4060 msgstr "" 4061 4061 4062 #: Services/VideoPostType.php:40 34062 #: Services/VideoPostType.php:405 4063 4063 msgctxt "admin menu" 4064 4064 msgid "Media" 4065 4065 msgstr "" 4066 4066 4067 #: Services/VideoPostType.php:40 44067 #: Services/VideoPostType.php:406 4068 4068 msgctxt "add new on admin bar" 4069 4069 msgid "Presto Media" 4070 4070 msgstr "" 4071 4071 4072 #: Services/VideoPostType.php:40 54072 #: Services/VideoPostType.php:407 4073 4073 msgctxt "Media" 4074 4074 msgid "Add New" 4075 4075 msgstr "" 4076 4076 4077 #: Services/VideoPostType.php:40 64077 #: Services/VideoPostType.php:408 4078 4078 msgid "Add New Media" 4079 4079 msgstr "" 4080 4080 4081 #: Services/VideoPostType.php:40 74081 #: Services/VideoPostType.php:409 4082 4082 msgid "New Media" 4083 4083 msgstr "" 4084 4084 4085 #: Services/VideoPostType.php:4 094085 #: Services/VideoPostType.php:411 4086 4086 msgid "View Media" 4087 4087 msgstr "" 4088 4088 4089 #: Services/VideoPostType.php:41 14089 #: Services/VideoPostType.php:413 4090 4090 msgid "Search Media" 4091 4091 msgstr "" 4092 4092 4093 #: Services/VideoPostType.php:41 24093 #: Services/VideoPostType.php:414 4094 4094 msgid "No Media found." 4095 4095 msgstr "" 4096 4096 4097 #: Services/VideoPostType.php:41 34097 #: Services/VideoPostType.php:415 4098 4098 msgid "No Media found in Trash." 4099 4099 msgstr "" 4100 4100 4101 #: Services/VideoPostType.php:41 44101 #: Services/VideoPostType.php:416 4102 4102 msgid "Filter Media list" 4103 4103 msgstr "" 4104 4104 4105 #: Services/VideoPostType.php:41 54105 #: Services/VideoPostType.php:417 4106 4106 msgid "Media list navigation" 4107 4107 msgstr "" 4108 4108 4109 #: Services/VideoPostType.php:41 64109 #: Services/VideoPostType.php:418 4110 4110 msgid "Media list" 4111 4111 msgstr "" 4112 4112 4113 #: Services/VideoPostType.php:41 74113 #: Services/VideoPostType.php:419 4114 4114 msgid "Media published." 4115 4115 msgstr "" 4116 4116 4117 #: Services/VideoPostType.php:4 184117 #: Services/VideoPostType.php:420 4118 4118 msgid "Media published privately." 4119 4119 msgstr "" 4120 4120 4121 #: Services/VideoPostType.php:4 194121 #: Services/VideoPostType.php:421 4122 4122 msgid "Media reverted to draft." 4123 4123 msgstr "" 4124 4124 4125 #: Services/VideoPostType.php:42 04125 #: Services/VideoPostType.php:422 4126 4126 msgid "Media scheduled." 4127 4127 msgstr "" 4128 4128 4129 #: Services/VideoPostType.php:42 14129 #: Services/VideoPostType.php:423 4130 4130 msgid "Media updated." 4131 4131 msgstr "" 4132 4132 4133 #: Services/VideoPostType.php:4 684133 #: Services/VideoPostType.php:471 4134 4134 #, php-format 4135 4135 msgid "Show all %s" -
presto-player/trunk/presto-player.php
r3468484 r3484635 4 4 * Plugin URI: http://prestoplayer.com 5 5 * Description: A beautiful, fast media player for WordPress. 6 * Version: 4.1. 06 * Version: 4.1.1 7 7 * Author: Presto Made, Inc 8 8 * Author URI: https://prestoplayer.com/ -
presto-player/trunk/readme.txt
r3468484 r3484635 5 5 Requires at least: 6.3 6 6 Tested up to: 6.9 7 Stable tag: 4.1. 07 Stable tag: 4.1.1 8 8 Requires PHP: 7.3 9 9 License: GPLv2 or later … … 157 157 == Changelog == 158 158 159 = 4.1.1 = 160 * Security: Multiple security hardening improvements. 161 159 162 = 4.1.0 = 160 163 * New: Automatic caption generation with BunnyCDN. -
presto-player/trunk/vendor/composer/installed.php
r3468484 r3484635 2 2 'root' => array( 3 3 'name' => 'course/player', 4 'pretty_version' => 'v4.1. 0',5 'version' => '4.1. 0.0',6 'reference' => ' 2bd337ed2bb551577b4496ecd06fed90574dc6d4',4 'pretty_version' => 'v4.1.1', 5 'version' => '4.1.1.0', 6 'reference' => '788f19da99c0e35e11897dd50e9a1aae24c68e63', 7 7 'type' => 'project', 8 8 'install_path' => __DIR__ . '/../../', … … 48 48 ), 49 49 'course/player' => array( 50 'pretty_version' => 'v4.1. 0',51 'version' => '4.1. 0.0',52 'reference' => ' 2bd337ed2bb551577b4496ecd06fed90574dc6d4',50 'pretty_version' => 'v4.1.1', 51 'version' => '4.1.1.0', 52 'reference' => '788f19da99c0e35e11897dd50e9a1aae24c68e63', 53 53 'type' => 'project', 54 54 'install_path' => __DIR__ . '/../../',
Note: See TracChangeset
for help on using the changeset viewer.