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
/**
* Download handler
*
* 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 {
/**
2014-05-28 13:52:50 +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 () {
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
}
/**
* Check if we need to download a file and check validity
*/
2014-05-28 13:52:50 +00:00
public static function download_product () {
2014-10-24 17:21:17 +00:00
if ( ! isset ( $_GET [ 'download_file' ] ) || ! isset ( $_GET [ 'order' ] ) || ! isset ( $_GET [ 'email' ] ) ) {
return ;
}
2013-08-09 16:11:15 +00:00
2014-10-24 17:21:17 +00:00
$product_id = absint ( $_GET [ 'download_file' ] );
$_product = wc_get_product ( $product_id );
$order_key = wc_clean ( $_GET [ 'order' ] );
$email = sanitize_email ( str_replace ( ' ' , '+' , $_GET [ 'email' ] ) );
$download_id = wc_clean ( isset ( $_GET [ 'key' ] ) ? preg_replace ( '/\s+/' , ' ' , $_GET [ 'key' ] ) : '' );
2013-08-09 16:11:15 +00:00
2014-10-24 22:08:50 +00:00
if ( ! $_product || ! $_product -> exists () ) {
self :: download_error ( __ ( 'Product no longer exists.' , 'woocommerce' ), '' , 403 );
}
2014-10-24 17:21:17 +00:00
if ( ! is_email ( $email ) ) {
2014-10-24 21:50:19 +00:00
self :: download_error ( __ ( 'Invalid email address.' , 'woocommerce' ), '' , 403 );
2014-10-24 17:21:17 +00:00
}
2013-08-09 16:11:15 +00:00
2014-10-24 17:21:17 +00:00
$download_data = self :: get_download_data ( array (
'product_id' => $product_id ,
'order_key' => $order_key ,
'email' => $email ,
'download_id' => $download_id
) );
2013-08-09 16:11:15 +00:00
2014-10-24 17:21:17 +00:00
if ( ! $download_data ) {
2014-10-24 21:50:19 +00:00
self :: download_error ( __ ( 'Invalid download.' , '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
self :: check_current_user_can_download ( $download_data );
self :: count_download ( $download_data );
do_action ( 'woocommerce_download_product' , $email , $order_key , $product_id , $download_data -> user_id , $download_data -> download_id , $download_data -> order_id );
self :: download ( $_product -> get_file_download_path ( $download_id ), $product_id );
}
2013-08-09 16:11:15 +00:00
2014-10-24 17:21:17 +00:00
/**
* Get a download from the database .
*
* @ param array $args Contains email , order key , product id and download id
* @ return object
*/
public static function get_download_data ( $args = array () ) {
global $wpdb ;
2013-08-09 16:11:15 +00:00
2014-10-24 17:21:17 +00:00
$query = " SELECT * FROM " . $wpdb -> prefix . " woocommerce_downloadable_product_permissions " ;
$query .= " WHERE user_email = %s " ;
$query .= " AND order_key = %s " ;
$query .= " AND product_id = %s " ;
2013-08-09 16:11:15 +00:00
2014-10-24 17:21:17 +00:00
if ( $args [ 'download_id' ] ) {
$query .= " AND download_id = %s " ;
}
2013-08-09 16:11:15 +00:00
2014-10-24 17:21:17 +00:00
return $wpdb -> get_row ( $wpdb -> prepare ( $query , array ( $args [ 'email' ], $args [ 'order_key' ], $args [ 'product_id' ], $args [ 'download_id' ] ) ) );
}
2013-08-09 16:11:15 +00:00
2014-10-24 17:21:17 +00:00
/**
* Perform checks to see if the current user can download the file
* @ param object $download_data
*/
public static function check_current_user_can_download ( $download_data ) {
2014-10-24 22:08:50 +00:00
try {
if ( $download_data -> order_id && ( $order = wc_get_order ( $download_data -> order_id ) ) && ! $order -> is_download_permitted () ) {
throw new Exception ( __ ( 'Invalid order.' , 'woocommerce' ) );
} elseif ( '0' == $download_data -> downloads_remaining ) {
throw new Exception ( __ ( 'Sorry, you have reached your download limit for this file' , 'woocommerce' ) );
} elseif ( $download_data -> access_expires > 0 && strtotime ( $download_data -> access_expires ) < current_time ( 'timestamp' ) ) {
throw new Exception ( __ ( 'Sorry, this download has expired' , 'woocommerce' ) );
} elseif ( $download_data -> user_id && get_option ( 'woocommerce_downloads_require_login' ) === 'yes' ) {
if ( ! is_user_logged_in () && wc_get_page_id ( 'myaccount' ) ) {
2014-10-24 17:21:17 +00:00
wp_safe_redirect ( add_query_arg ( 'wc_error' , urlencode ( __ ( 'You must be logged in to download files.' , 'woocommerce' ) ), get_permalink ( wc_get_page_id ( 'myaccount' ) ) ) );
exit ;
2014-10-24 22:08:50 +00:00
} elseif ( ! is_user_logged_in () ) {
2014-10-24 21:50:19 +00:00
self :: download_error ( __ ( 'You must be logged in to download files.' , 'woocommerce' ) . ' <a href="' . esc_url ( wp_login_url ( get_permalink ( wc_get_page_id ( 'myaccount' ) ) ) ) . '" class="wc-forward">' . __ ( 'Login' , 'woocommerce' ) . '</a>' , __ ( 'Log in to Download Files' , 'woocommerce' ), 403 );
2014-10-24 22:08:50 +00:00
} elseif ( ! current_user_can ( 'download_file' , $download_data ) ) {
throw new Exception ( __ ( 'This is not your download link.' , 'woocommerce' ) );
2014-02-26 11:54:16 +00:00
}
2013-08-09 16:11:15 +00:00
}
2014-10-24 22:08:50 +00:00
} catch ( Exception $e ) {
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-24 17:21:17 +00:00
/**
* Log the download + increase counts
* @ param object $download_data
*/
public static function count_download ( $download_data ) {
global $wpdb ;
if ( $download_data -> downloads_remaining > 0 ) {
$wpdb -> update (
$wpdb -> prefix . " woocommerce_downloadable_product_permissions " ,
array (
'downloads_remaining' => $download_data -> downloads_remaining - 1 ,
),
array (
'permission_id' => absint ( $download_data -> permission_id ),
),
array ( '%d' ),
array ( '%d' )
);
2013-08-09 16:11:15 +00:00
}
2014-10-24 17:21:17 +00:00
$wpdb -> update (
$wpdb -> prefix . " woocommerce_downloadable_product_permissions " ,
array (
2014-10-24 21:50:19 +00:00
'download_count' => $download_data -> download_count + 1 ,
2014-10-24 17:21:17 +00:00
),
array (
'permission_id' => absint ( $download_data -> permission_id ),
),
array ( '%d' ),
array ( '%d' )
);
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 );
// Trigger download via one of the methods
do_action ( 'woocommerce_download_file_' . $file_download_method , $file_path , $filename );
}
/**
* Redirect to a file to start the download
* @ param string $file_path
* @ param string $filename
*/
public static function download_file_redirect ( $file_path , $filename = '' ) {
header ( 'Location: ' . $file_path );
exit ;
}
/**
* Parse file path and see if its remote or local
* @ param string $file_path
* @ return array
*/
public static function parse_file_path ( $file_path ) {
2014-04-07 14:09:11 +00:00
$remote_file = true ;
$parsed_file_path = parse_url ( $file_path );
2014-05-25 21:10:23 +00:00
2014-04-07 14:09:11 +00:00
$wp_uploads = wp_upload_dir ();
$wp_uploads_dir = $wp_uploads [ 'basedir' ];
$wp_uploads_url = $wp_uploads [ 'baseurl' ];
if ( ( ! 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
2014-10-24 17:21:17 +00:00
/// This is an absolute path
2014-04-07 14:09:11 +00:00
$remote_file = false ;
2013-08-09 16:11:15 +00:00
2014-04-07 14:09:11 +00:00
} elseif ( strpos ( $file_path , $wp_uploads_url ) !== false ) {
2013-08-09 16:11:15 +00:00
2014-10-24 17:21:17 +00:00
// This is a local file given by URL so we need to figure out the path
2014-04-07 14:09:11 +00:00
$remote_file = false ;
$file_path = str_replace ( $wp_uploads_url , $wp_uploads_dir , $file_path );
2013-08-09 16:11:15 +00:00
2014-04-07 14:09:11 +00:00
} elseif ( is_multisite () && ( strpos ( $file_path , network_site_url ( '/' , 'http' ) ) !== false || strpos ( $file_path , network_site_url ( '/' , 'https' ) ) !== false ) ) {
2014-10-24 17:21:17 +00:00
// This is a local file outside of wp-content so figure out the path
2014-04-07 14:09:11 +00:00
$remote_file = false ;
2013-08-09 16:11:15 +00:00
// Try to replace network url
2014-04-07 14:09:11 +00:00
$file_path = str_replace ( network_site_url ( '/' , 'https' ), ABSPATH , $file_path );
$file_path = str_replace ( network_site_url ( '/' , 'http' ), ABSPATH , $file_path );
// Try to replace upload URL
$file_path = str_replace ( $wp_uploads_url , $wp_uploads_dir , $file_path );
2013-08-09 16:11:15 +00:00
2014-04-07 14:09:11 +00:00
} elseif ( strpos ( $file_path , site_url ( '/' , 'http' ) ) !== false || strpos ( $file_path , site_url ( '/' , 'https' ) ) !== false ) {
2014-10-24 17:21:17 +00:00
// This is a local file outside of wp-content so figure out the path
2014-04-07 14:09:11 +00:00
$remote_file = false ;
$file_path = str_replace ( site_url ( '/' , 'https' ), ABSPATH , $file_path );
$file_path = str_replace ( site_url ( '/' , 'http' ), ABSPATH , $file_path );
2013-08-09 16:11:15 +00:00
2014-04-07 14:09:11 +00:00
} elseif ( file_exists ( ABSPATH . $file_path ) ) {
2014-05-25 21:10:23 +00:00
2014-10-24 17:21:17 +00:00
// Path needs an abspath to work
2013-08-09 16:11:15 +00:00
$remote_file = false ;
2014-04-07 14:09:11 +00:00
$file_path = ABSPATH . $file_path ;
}
2013-08-09 16:11:15 +00:00
2014-10-24 17:21:17 +00:00
return array (
'remote_file' => $remote_file ,
'file_path' => $file_path
);
}
/**
* Download a file using X - Sendfile , X - Lighttpd - Sendfile , or X - Accel - Redirect if available
* @ 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 );
extract ( $parsed_file_path );
2014-10-24 17:21:17 +00:00
// Path fix - kudos to Jason Judge
if ( getcwd () ) {
$xsendfile_path = trim ( preg_replace ( '`^' . str_replace ( '\\' , '/' , getcwd () ) . '`' , '' , $file_path ), '/' );
}
if ( function_exists ( 'apache_get_modules' ) && in_array ( 'mod_xsendfile' , apache_get_modules () ) ) {
self :: download_headers ( $file_path , $filename );
header ( " Content-Disposition: attachment; filename= \" " . $filename . " \" ; " );
header ( " X-Sendfile: $xsendfile_path " );
exit ;
} elseif ( stristr ( getenv ( 'SERVER_SOFTWARE' ), 'lighttpd' ) ) {
self :: download_headers ( $file_path , $filename );
header ( " Content-Disposition: attachment; filename= \" " . $filename . " \" ; " );
header ( " X-Lighttpd-Sendfile: $xsendfile_path " );
exit ;
} elseif ( stristr ( getenv ( 'SERVER_SOFTWARE' ), 'nginx' ) || stristr ( getenv ( 'SERVER_SOFTWARE' ), 'cherokee' ) ) {
self :: download_headers ( $file_path , $filename );
header ( " Content-Disposition: attachment; filename= \" " . $filename . " \" ; " );
header ( " X-Accel-Redirect: / $xsendfile_path " );
exit ;
}
// Fallback
self :: download_file_force ( $file_path , $filename );
}
/**
* Force download - this is the default method
* @ 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 );
extract ( $parsed_file_path );
2013-08-09 16:11:15 +00:00
2014-10-24 17:21:17 +00:00
self :: download_headers ( $file_path , $filename );
if ( ! self :: readfile_chunked ( $file_path ) ) {
if ( $remote_file ) {
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 ;
}
/**
* Get content type of a download
* @ param string $file_path
* @ return string
*/
public 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 ;
}
/**
* Set headers for the download
* @ param string $file_path
* @ param string $filename
*/
public static function download_headers ( $file_path , $filename ) {
2014-02-26 11:54:16 +00:00
if ( ! ini_get ( 'safe_mode' ) ) {
2013-08-09 16:11:15 +00:00
@ set_time_limit ( 0 );
2014-02-26 11:54:16 +00:00
}
2013-08-09 16:11:15 +00:00
2014-02-26 11:54:16 +00:00
if ( function_exists ( 'get_magic_quotes_runtime' ) && get_magic_quotes_runtime () ) {
2013-08-09 16:11:15 +00:00
@ set_magic_quotes_runtime ( 0 );
2014-02-26 11:54:16 +00:00
}
2013-08-09 16:11:15 +00:00
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
@ session_write_close ();
@ ini_set ( 'zlib.output_compression' , 'Off' );
2013-12-19 09:14:39 +00:00
/**
* Prevents errors , for example : transfer closed with 3 bytes remaining to read
*/
2014-05-25 21:10:23 +00:00
if ( ob_get_length () ) {
if ( ob_get_level () ) {
$levels = ob_get_level ();
2014-01-03 02:35:17 +00:00
2014-05-25 21:10:23 +00:00
for ( $i = 0 ; $i < $levels ; $i ++ ) {
ob_end_clean (); // Zip corruption fix
}
} else {
ob_end_clean (); // Clear the output buffer
}
2013-12-19 09:14:39 +00:00
}
2013-08-09 16:11:15 +00:00
2014-10-24 17:21:17 +00:00
if ( is_ssl () && ! empty ( $GLOBALS [ 'is_IE' ] ) ) {
2013-08-09 16:11:15 +00:00
// IE bug prevents download via SSL when Cache Control and Pragma no-cache headers set.
header ( 'Expires: Wed, 11 Jan 1984 05:00:00 GMT' );
header ( 'Cache-Control: private' );
} else {
nocache_headers ();
}
2013-11-14 12:40:33 +00:00
header ( " X-Robots-Tag: noindex, nofollow " , true );
2014-10-24 17:21:17 +00:00
header ( " Content-Type: " . self :: get_download_content_type ( $file_path ) );
2013-08-09 16:11:15 +00:00
header ( " Content-Description: File Transfer " );
2014-03-14 10:04:41 +00:00
header ( " Content-Disposition: attachment; filename= \" " . $filename . " \" ; " );
2013-08-09 16:11:15 +00:00
header ( " Content-Transfer-Encoding: binary " );
2014-02-26 11:54:16 +00:00
if ( $size = @ filesize ( $file_path ) ) {
2013-08-09 16:11:15 +00:00
header ( " Content-Length: " . $size );
2014-02-26 11:54:16 +00:00
}
2013-08-09 16:11:15 +00:00
}
/**
* readfile_chunked
2014-10-24 16:06:30 +00:00
*
2013-08-09 16:11:15 +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 ) {
2014-10-24 21:50:19 +00:00
$chunksize = 1024 * 1024 ;
$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 ) ) {
echo @ fread ( $handle , $chunksize );
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 );
}
/**
* Die with an error message if the download fails
* @ param string $message
* @ param string $title
* @ param integer $status
*/
public static function download_error ( $message , $title = '' , $status = 404 ) {
if ( ! strstr ( $message , '<a ' ) ) {
$message .= ' <a href="' . esc_url ( home_url () ) . '" class="wc-forward">' . __ ( 'Go to homepage' , 'woocommerce' ) . '</a>' ;
}
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 ();