2013-08-09 16:11:15 +00:00
< ? php
2014-05-28 13:52:50 +00:00
if ( ! defined ( 'ABSPATH' ) ) {
exit ; // Exit if accessed directly
}
2013-08-09 16:11:15 +00:00
/**
2015-11-03 13:31:20 +00:00
* Download handler .
2013-08-09 16:11:15 +00:00
*
* Handle digital downloads .
*
* @ class WC_Download_Handler
2014-05-28 13:52:50 +00:00
* @ version 2.2 . 0
2013-08-09 16:11:15 +00:00
* @ package WooCommerce / Classes
* @ category Class
* @ author WooThemes
*/
class WC_Download_Handler {
/**
2015-11-03 13:31:20 +00:00
* Hook in methods .
2013-08-09 16:11:15 +00:00
*/
2014-05-28 13:52:50 +00:00
public static function init () {
2017-02-20 13:36:15 +00:00
if ( isset ( $_GET [ 'download_file' ], $_GET [ 'order' ], $_GET [ 'email' ] ) ) {
2014-10-27 12:25:05 +00:00
add_action ( 'init' , array ( __CLASS__ , 'download_product' ) );
}
2014-10-24 17:21:17 +00:00
add_action ( 'woocommerce_download_file_redirect' , array ( __CLASS__ , 'download_file_redirect' ), 10 , 2 );
add_action ( 'woocommerce_download_file_xsendfile' , array ( __CLASS__ , 'download_file_xsendfile' ), 10 , 2 );
add_action ( 'woocommerce_download_file_force' , array ( __CLASS__ , 'download_file_force' ), 10 , 2 );
2013-08-09 16:11:15 +00:00
}
/**
2015-11-03 13:31:20 +00:00
* Check if we need to download a file and check validity .
2013-08-09 16:11:15 +00:00
*/
2014-05-28 13:52:50 +00:00
public static function download_product () {
2016-11-18 17:13:02 +00:00
$product_id = absint ( $_GET [ 'download_file' ] );
$product = wc_get_product ( $product_id );
$data_store = WC_Data_Store :: load ( 'customer-download' );
2013-08-09 16:11:15 +00:00
2018-01-03 10:55:57 +00:00
if ( ! $product || empty ( $_GET [ 'key' ] ) || empty ( $_GET [ 'order' ] ) ) {
2014-10-27 12:25:05 +00:00
self :: download_error ( __ ( 'Invalid download link.' , 'woocommerce' ) );
}
2013-08-09 16:11:15 +00:00
2016-11-18 19:29:37 +00:00
$download_ids = $data_store -> get_downloads ( array (
2016-11-18 17:13:02 +00:00
'user_email' => sanitize_email ( str_replace ( ' ' , '+' , $_GET [ 'email' ] ) ),
'order_key' => wc_clean ( $_GET [ 'order' ] ),
'product_id' => $product_id ,
'download_id' => wc_clean ( preg_replace ( '/\s+/' , ' ' , $_GET [ 'key' ] ) ),
'orderby' => 'downloads_remaining' ,
'order' => 'DESC' ,
'limit' => 1 ,
2016-11-18 19:29:37 +00:00
'return' => 'ids' ,
2016-11-18 17:13:02 +00:00
) );
2013-08-09 16:11:15 +00:00
2016-11-18 17:13:02 +00:00
if ( empty ( $download_ids ) ) {
self :: download_error ( __ ( 'Invalid download link.' , 'woocommerce' ) );
2014-10-24 17:21:17 +00:00
}
2013-08-09 16:11:15 +00:00
2016-11-18 17:13:02 +00:00
$download = new WC_Customer_Download ( current ( $download_ids ) );
self :: check_order_is_valid ( $download );
self :: check_downloads_remaining ( $download );
self :: check_download_expiry ( $download );
self :: check_download_login_required ( $download );
do_action (
'woocommerce_download_product' ,
$download -> get_user_email (),
$download -> get_order_key (),
$download -> get_product_id (),
$download -> get_user_id (),
$download -> get_download_id (),
$download -> get_order_id ()
);
2017-08-23 03:15:23 +00:00
$download -> save ();
2017-07-30 22:38:17 +00:00
2017-08-23 03:15:23 +00:00
// Track the download in logs and change remaining/counts.
2017-07-31 03:44:11 +00:00
$current_user_id = get_current_user_id ();
$ip_address = WC_Geolocation :: get_ip_address ();
2017-08-23 03:15:23 +00:00
$download -> track_download ( $current_user_id > 0 ? $current_user_id : null , ! empty ( $ip_address ) ? $ip_address : null );
2016-05-05 08:28:36 +00:00
2016-11-18 17:13:02 +00:00
self :: download ( $product -> get_file_download_path ( $download -> get_download_id () ), $download -> get_product_id () );
2014-10-27 10:33:30 +00:00
}
/**
2015-11-03 13:31:20 +00:00
* Check if an order is valid for downloading from .
2016-11-18 17:13:02 +00:00
* @ param WC_Customer_Download $download
2014-10-27 11:01:16 +00:00
* @ access private
2014-10-27 10:33:30 +00:00
*/
2016-11-18 17:13:02 +00:00
private static function check_order_is_valid ( $download ) {
if ( $download -> get_order_id () && ( $order = wc_get_order ( $download -> get_order_id () ) ) && ! $order -> is_download_permitted () ) {
2014-10-24 22:08:50 +00:00
self :: download_error ( __ ( 'Invalid order.' , 'woocommerce' ), '' , 403 );
2014-10-24 17:21:17 +00:00
}
}
2013-08-09 16:11:15 +00:00
2014-10-27 10:33:30 +00:00
/**
2015-11-03 13:31:20 +00:00
* Check if there are downloads remaining .
2016-11-18 17:13:02 +00:00
* @ param WC_Customer_Download $download
2014-10-27 11:01:16 +00:00
* @ access private
2014-10-27 10:33:30 +00:00
*/
2016-11-18 17:13:02 +00:00
private static function check_downloads_remaining ( $download ) {
2017-01-03 15:03:55 +00:00
if ( '' !== $download -> get_downloads_remaining () && 0 >= $download -> get_downloads_remaining () ) {
2014-10-27 10:33:30 +00:00
self :: download_error ( __ ( 'Sorry, you have reached your download limit for this file' , 'woocommerce' ), '' , 403 );
}
}
/**
2015-11-03 13:31:20 +00:00
* Check if the download has expired .
2016-11-18 17:13:02 +00:00
* @ param WC_Customer_Download $download
2014-10-27 11:01:16 +00:00
* @ access private
2014-10-27 10:33:30 +00:00
*/
2016-11-18 17:13:02 +00:00
private static function check_download_expiry ( $download ) {
2017-03-13 23:54:33 +00:00
if ( ! is_null ( $download -> get_access_expires () ) && $download -> get_access_expires () -> getTimestamp () < strtotime ( 'midnight' , current_time ( 'timestamp' , true ) ) ) {
2014-10-27 10:33:30 +00:00
self :: download_error ( __ ( 'Sorry, this download has expired' , 'woocommerce' ), '' , 403 );
}
}
/**
2015-11-03 13:31:20 +00:00
* Check if a download requires the user to login first .
2016-11-18 17:13:02 +00:00
* @ param WC_Customer_Download $download
2014-10-27 11:01:16 +00:00
* @ access private
2014-10-27 10:33:30 +00:00
*/
2016-11-18 17:13:02 +00:00
private static function check_download_login_required ( $download ) {
if ( $download -> get_user_id () && 'yes' === get_option ( 'woocommerce_downloads_require_login' ) ) {
2014-10-27 13:38:24 +00:00
if ( ! is_user_logged_in () ) {
if ( wc_get_page_id ( 'myaccount' ) ) {
2015-02-15 19:13:22 +00:00
wp_safe_redirect ( add_query_arg ( 'wc_error' , urlencode ( __ ( 'You must be logged in to download files.' , 'woocommerce' ) ), wc_get_page_permalink ( 'myaccount' ) ) );
2014-10-27 13:38:24 +00:00
exit ;
} else {
2015-02-15 19:13:22 +00:00
self :: download_error ( __ ( 'You must be logged in to download files.' , 'woocommerce' ) . ' <a href="' . esc_url ( wp_login_url ( wc_get_page_permalink ( 'myaccount' ) ) ) . '" class="wc-forward">' . __ ( 'Login' , 'woocommerce' ) . '</a>' , __ ( 'Log in to Download Files' , 'woocommerce' ), 403 );
2014-10-27 13:38:24 +00:00
}
2016-11-18 17:13:02 +00:00
} elseif ( ! current_user_can ( 'download_file' , $download ) ) {
2014-10-27 10:33:30 +00:00
self :: download_error ( __ ( 'This is not your download link.' , 'woocommerce' ), '' , 403 );
}
}
}
2014-10-24 17:21:17 +00:00
/**
2016-11-18 17:13:02 +00:00
* @ deprecated
2017-05-15 11:50:52 +00:00
*
* @ param $download_data
2014-10-24 17:21:17 +00:00
*/
2016-11-18 17:13:02 +00:00
public static function count_download ( $download_data ) {}
2013-08-09 16:11:15 +00:00
/**
* Download a file - hook into init function .
2014-10-24 17:21:17 +00:00
* @ param string $file_path URL to file
* @ param integer $product_id of the product being downloaded
2013-08-09 16:11:15 +00:00
*/
2014-05-28 13:52:50 +00:00
public static function download ( $file_path , $product_id ) {
2014-02-26 11:54:16 +00:00
if ( ! $file_path ) {
2014-10-24 21:50:19 +00:00
self :: download_error ( __ ( 'No file defined' , 'woocommerce' ) );
2014-02-26 11:54:16 +00:00
}
2013-08-09 16:11:15 +00:00
2014-10-24 17:21:17 +00:00
$filename = basename ( $file_path );
if ( strstr ( $filename , '?' ) ) {
$filename = current ( explode ( '?' , $filename ) );
2013-08-09 16:11:15 +00:00
}
2014-10-24 17:21:17 +00:00
$filename = apply_filters ( 'woocommerce_file_download_filename' , $filename , $product_id );
$file_download_method = apply_filters ( 'woocommerce_file_download_method' , get_option ( 'woocommerce_file_download_method' , 'force' ), $product_id );
2014-10-27 11:01:16 +00:00
// Add action to prevent issues in IE
add_action ( 'nocache_headers' , array ( __CLASS__ , 'ie_nocache_headers_fix' ) );
2014-10-24 17:21:17 +00:00
// Trigger download via one of the methods
do_action ( 'woocommerce_download_file_' . $file_download_method , $file_path , $filename );
}
/**
2015-11-03 13:31:20 +00:00
* Redirect to a file to start the download .
2014-10-24 17:21:17 +00:00
* @ param string $file_path
* @ param string $filename
*/
public static function download_file_redirect ( $file_path , $filename = '' ) {
header ( 'Location: ' . $file_path );
exit ;
}
/**
2015-11-03 13:31:20 +00:00
* Parse file path and see if its remote or local .
2014-10-24 17:21:17 +00:00
* @ param string $file_path
* @ return array
*/
public static function parse_file_path ( $file_path ) {
2014-10-27 13:38:24 +00:00
$wp_uploads = wp_upload_dir ();
$wp_uploads_dir = $wp_uploads [ 'basedir' ];
$wp_uploads_url = $wp_uploads [ 'baseurl' ];
2017-07-21 10:11:17 +00:00
/**
* Replace uploads dir , site url etc with absolute counterparts if we can .
* Note the str_replace on site_url is on purpose , so if https is forced
* via filters we can still do the string replacement on a HTTP file .
*/
2014-10-27 13:38:24 +00:00
$replacements = array (
2017-07-21 10:11:17 +00:00
$wp_uploads_url => $wp_uploads_dir ,
network_site_url ( '/' , 'https' ) => ABSPATH ,
str_replace ( 'https:' , 'http:' , network_site_url ( '/' , 'http' ) ) => ABSPATH ,
site_url ( '/' , 'https' ) => ABSPATH ,
str_replace ( 'https:' , 'http:' , site_url ( '/' , 'http' ) ) => ABSPATH ,
2014-10-27 13:38:24 +00:00
);
2013-08-09 16:11:15 +00:00
2014-10-27 13:38:24 +00:00
$file_path = str_replace ( array_keys ( $replacements ), array_values ( $replacements ), $file_path );
$parsed_file_path = parse_url ( $file_path );
$remote_file = true ;
2014-04-07 14:09:11 +00:00
2014-10-27 13:38:24 +00:00
// See if path needs an abspath prepended to work
if ( file_exists ( ABSPATH . $file_path ) ) {
2014-04-07 14:09:11 +00:00
$remote_file = false ;
2014-10-27 13:38:24 +00:00
$file_path = ABSPATH . $file_path ;
2014-05-25 21:10:23 +00:00
2017-03-24 11:48:32 +00:00
} elseif ( '/wp-content' === substr ( $file_path , 0 , 11 ) ) {
$remote_file = false ;
$file_path = realpath ( WP_CONTENT_DIR . substr ( $file_path , 11 ) );
2014-10-27 13:38:24 +00:00
// Check if we have an absolute path
} elseif ( ( ! isset ( $parsed_file_path [ 'scheme' ] ) || ! in_array ( $parsed_file_path [ 'scheme' ], array ( 'http' , 'https' , 'ftp' ) ) ) && isset ( $parsed_file_path [ 'path' ] ) && file_exists ( $parsed_file_path [ 'path' ] ) ) {
2013-08-09 16:11:15 +00:00
$remote_file = false ;
2014-10-27 13:38:24 +00:00
$file_path = $parsed_file_path [ 'path' ];
2014-04-07 14:09:11 +00:00
}
2013-08-09 16:11:15 +00:00
2014-10-24 17:21:17 +00:00
return array (
'remote_file' => $remote_file ,
2016-08-27 01:46:45 +00:00
'file_path' => $file_path ,
2014-10-24 17:21:17 +00:00
);
}
/**
2015-11-03 13:31:20 +00:00
* Download a file using X - Sendfile , X - Lighttpd - Sendfile , or X - Accel - Redirect if available .
2014-10-24 17:21:17 +00:00
* @ param string $file_path
* @ param string $filename
*/
public static function download_file_xsendfile ( $file_path , $filename ) {
2014-10-24 21:50:19 +00:00
$parsed_file_path = self :: parse_file_path ( $file_path );
2014-10-24 17:21:17 +00:00
if ( function_exists ( 'apache_get_modules' ) && in_array ( 'mod_xsendfile' , apache_get_modules () ) ) {
2015-07-27 11:44:29 +00:00
self :: download_headers ( $parsed_file_path [ 'file_path' ], $filename );
header ( " X-Sendfile: " . $parsed_file_path [ 'file_path' ] );
2014-10-24 17:21:17 +00:00
exit ;
} elseif ( stristr ( getenv ( 'SERVER_SOFTWARE' ), 'lighttpd' ) ) {
2015-07-27 11:44:29 +00:00
self :: download_headers ( $parsed_file_path [ 'file_path' ], $filename );
header ( " X-Lighttpd-Sendfile: " . $parsed_file_path [ 'file_path' ] );
2014-10-24 17:21:17 +00:00
exit ;
} elseif ( stristr ( getenv ( 'SERVER_SOFTWARE' ), 'nginx' ) || stristr ( getenv ( 'SERVER_SOFTWARE' ), 'cherokee' ) ) {
2015-07-27 11:44:29 +00:00
self :: download_headers ( $parsed_file_path [ 'file_path' ], $filename );
$xsendfile_path = trim ( preg_replace ( '`^' . str_replace ( '\\' , '/' , getcwd () ) . '`' , '' , $parsed_file_path [ 'file_path' ] ), '/' );
2014-10-24 17:21:17 +00:00
header ( " X-Accel-Redirect: / $xsendfile_path " );
exit ;
}
// Fallback
self :: download_file_force ( $file_path , $filename );
}
/**
2015-11-03 13:31:20 +00:00
* Force download - this is the default method .
2014-10-24 17:21:17 +00:00
* @ param string $file_path
* @ param string $filename
*/
public static function download_file_force ( $file_path , $filename ) {
2014-10-24 21:50:19 +00:00
$parsed_file_path = self :: parse_file_path ( $file_path );
2015-07-27 11:44:29 +00:00
self :: download_headers ( $parsed_file_path [ 'file_path' ], $filename );
2014-10-24 17:21:17 +00:00
2015-07-27 11:44:29 +00:00
if ( ! self :: readfile_chunked ( $parsed_file_path [ 'file_path' ] ) ) {
if ( $parsed_file_path [ 'remote_file' ] ) {
2014-10-24 17:21:17 +00:00
self :: download_file_redirect ( $file_path );
} else {
2014-10-24 21:50:19 +00:00
self :: download_error ( __ ( 'File not found' , 'woocommerce' ) );
2014-10-24 17:21:17 +00:00
}
2013-08-09 16:11:15 +00:00
}
2014-10-24 17:21:17 +00:00
exit ;
}
/**
2015-11-03 13:31:20 +00:00
* Get content type of a download .
2014-10-24 17:21:17 +00:00
* @ param string $file_path
* @ return string
2014-10-27 11:01:16 +00:00
* @ access private
2014-10-24 17:21:17 +00:00
*/
2014-10-27 11:01:16 +00:00
private static function get_download_content_type ( $file_path ) {
2013-08-09 16:11:15 +00:00
$file_extension = strtolower ( substr ( strrchr ( $file_path , " . " ), 1 ) );
$ctype = " application/force-download " ;
foreach ( get_allowed_mime_types () as $mime => $type ) {
$mimes = explode ( '|' , $mime );
if ( in_array ( $file_extension , $mimes ) ) {
$ctype = $type ;
break ;
}
}
2014-10-24 17:21:17 +00:00
return $ctype ;
}
/**
2015-11-03 13:31:20 +00:00
* Set headers for the download .
2014-10-24 17:21:17 +00:00
* @ param string $file_path
* @ param string $filename
2014-10-27 11:01:16 +00:00
* @ access private
2014-10-24 17:21:17 +00:00
*/
2014-10-27 11:01:16 +00:00
private static function download_headers ( $file_path , $filename ) {
self :: check_server_config ();
self :: clean_buffers ();
2017-11-08 15:07:00 +00:00
wc_nocache_headers ();
2014-10-27 11:01:16 +00:00
header ( " X-Robots-Tag: noindex, nofollow " , true );
header ( " Content-Type: " . self :: get_download_content_type ( $file_path ) );
header ( " Content-Description: File Transfer " );
header ( " Content-Disposition: attachment; filename= \" " . $filename . " \" ; " );
header ( " Content-Transfer-Encoding: binary " );
2016-07-11 14:56:35 +00:00
if ( $size = @ filesize ( $file_path ) ) {
header ( " Content-Length: " . $size );
}
2014-10-27 11:01:16 +00:00
}
/**
* Check and set certain server config variables to ensure downloads work as intended .
*/
private static function check_server_config () {
2016-06-06 15:55:27 +00:00
wc_set_time_limit ( 0 );
2015-07-15 15:14:32 +00:00
if ( function_exists ( 'get_magic_quotes_runtime' ) && get_magic_quotes_runtime () && version_compare ( phpversion (), '5.4' , '<' ) ) {
2017-11-09 18:36:29 +00:00
set_magic_quotes_runtime ( 0 ); // @codingStandardsIgnoreLine
2014-02-26 11:54:16 +00:00
}
if ( function_exists ( 'apache_setenv' ) ) {
2013-08-09 16:11:15 +00:00
@ apache_setenv ( 'no-gzip' , 1 );
2014-02-26 11:54:16 +00:00
}
2013-08-09 16:11:15 +00:00
@ ini_set ( 'zlib.output_compression' , 'Off' );
2014-10-27 11:01:16 +00:00
@ session_write_close ();
}
2013-08-09 16:11:15 +00:00
2014-10-27 11:01:16 +00:00
/**
2015-11-03 13:31:20 +00:00
* Clean all output buffers .
2014-10-27 11:01:16 +00:00
*
2015-11-03 13:31:20 +00:00
* Can prevent errors , for example : transfer closed with 3 bytes remaining to read .
2014-10-27 11:01:16 +00:00
*
* @ access private
*/
private static function clean_buffers () {
2014-11-12 16:15:47 +00:00
if ( ob_get_level () ) {
$levels = ob_get_level ();
for ( $i = 0 ; $i < $levels ; $i ++ ) {
@ ob_end_clean ();
2014-05-25 21:10:23 +00:00
}
2014-11-12 16:15:47 +00:00
} else {
@ ob_end_clean ();
2013-12-19 09:14:39 +00:00
}
2013-08-09 16:11:15 +00:00
}
/**
2015-11-03 13:31:20 +00:00
* readfile_chunked .
2014-10-24 16:06:30 +00:00
*
2015-11-03 13:31:20 +00:00
* Reads file in chunks so big downloads are possible without changing PHP . INI - http :// codeigniter . com / wiki / Download_helper_for_large_files /.
2014-10-24 16:06:30 +00:00
*
* @ param string $file
* @ return bool Success or fail
2013-08-09 16:11:15 +00:00
*/
2014-10-24 16:06:30 +00:00
public static function readfile_chunked ( $file ) {
2017-11-15 16:32:28 +00:00
if ( ! defined ( 'WC_CHUNK_SIZE' ) ) {
define ( 'WC_CHUNK_SIZE' , 1024 * 1024 );
}
2014-10-24 21:50:19 +00:00
$handle = @ fopen ( $file , 'r' );
2013-08-09 16:11:15 +00:00
2014-10-24 21:50:19 +00:00
if ( false === $handle ) {
return false ;
}
2013-08-09 16:11:15 +00:00
2014-10-24 21:50:19 +00:00
while ( ! @ feof ( $handle ) ) {
2017-11-15 16:32:28 +00:00
echo @ fread ( $handle , ( int ) WC_CHUNK_SIZE );
2013-08-09 16:11:15 +00:00
2014-10-24 21:50:19 +00:00
if ( ob_get_length () ) {
ob_flush ();
flush ();
}
2014-02-26 11:54:16 +00:00
}
2013-08-09 16:11:15 +00:00
2014-10-24 21:50:19 +00:00
return @ fclose ( $handle );
}
2014-10-27 11:01:16 +00:00
/**
2015-11-03 13:31:20 +00:00
* Filter headers for IE to fix issues over SSL .
2014-10-27 11:01:16 +00:00
*
* IE bug prevents download via SSL when Cache Control and Pragma no - cache headers set .
*
* @ param array $headers
* @ return array
*/
2014-10-27 13:38:24 +00:00
public static function ie_nocache_headers_fix ( $headers ) {
2014-10-27 11:01:16 +00:00
if ( is_ssl () && ! empty ( $GLOBALS [ 'is_IE' ] ) ) {
$headers [ 'Cache-Control' ] = 'private' ;
unset ( $headers [ 'Pragma' ] );
}
return $headers ;
}
2014-10-24 21:50:19 +00:00
/**
2015-11-03 13:31:20 +00:00
* Die with an error message if the download fails .
2014-10-24 21:50:19 +00:00
* @ param string $message
* @ param string $title
* @ param integer $status
2014-10-27 11:01:16 +00:00
* @ access private
2014-10-24 21:50:19 +00:00
*/
2014-10-27 11:01:16 +00:00
private static function download_error ( $message , $title = '' , $status = 404 ) {
2014-10-24 21:50:19 +00:00
if ( ! strstr ( $message , '<a ' ) ) {
2017-03-13 05:39:46 +00:00
$message .= ' <a href="' . esc_url ( wc_get_page_permalink ( 'shop' ) ) . '" class="wc-forward">' . esc_html__ ( 'Go to shop' , 'woocommerce' ) . '</a>' ;
2014-10-24 21:50:19 +00:00
}
wp_die ( $message , $title , array ( 'response' => $status ) );
2013-08-09 16:11:15 +00:00
}
}
2014-05-28 13:52:50 +00:00
WC_Download_Handler :: init ();