Skip to content

Instantly share code, notes, and snippets.

@kagg-design
Last active November 1, 2024 17:35
Show Gist options
  • Save kagg-design/241fdef594c326a33423ff74f6bd6115 to your computer and use it in GitHub Desktop.
Save kagg-design/241fdef594c326a33423ff74f6bd6115 to your computer and use it in GitHub Desktop.
A class to hide downloadable links and count downloads.
<?php
/**
* Class to hide file download links and count download number.
*
* @package kagg-downloader
*/
namespace GTS\Quote;
/**
* Class Downloader.
*
* Requires the following table in database:
*
* CREATE TABLE `wp_downloads` (
* `id` bigint(20) UNSIGNED NOT NULL,
* `url` varchar(355) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
* `download_link` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
* `count` bigint(20) UNSIGNED NOT NULL DEFAULT '0'
* ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
*/
class Downloader {
/**
* Class table name.
*/
private const TABLE = 'downloads';
/**
* Class table name.
*
* @var string $table
*/
protected static string $table;
/**
* Base of download links.
*
* @var string $link_base
*/
protected static string $link_base = '/downloads/';
/**
* Downloader constructor.
*/
public function __construct() {
global $wpdb;
self::$table = $wpdb->prefix . self::TABLE;
add_action( 'init', [ $this, 'rewrite_download_link' ] );
}
/**
* Get download link from url.
*
* @param string $url Download URL.
*
* @return string
*/
public static function get_link( string $url ): string {
global $wpdb;
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
$record = $wpdb->get_row(
$wpdb->prepare( "SELECT download_link FROM {$wpdb->prefix}downloads WHERE url = %s", $url )
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery
return $record->download_link ?? '';
}
/**
* Get url from download link.
*
* @param string $download_link Download link.
*
* @return string
*/
public static function get_url( string $download_link ): string {
global $wpdb;
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
$record = $wpdb->get_row(
$wpdb->prepare( "SELECT url FROM {$wpdb->prefix}downloads WHERE download_link = %s", $download_link )
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery
return $record->url ?? '';
}
/**
* Get download count.
*
* @param string $url Download URL.
*
* @return int
*/
public static function get_count( string $url ): int {
global $wpdb;
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
$record = $wpdb->get_row(
$wpdb->prepare( "SELECT count FROM {$wpdb->prefix}downloads WHERE url = %s", $url )
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery
return $record->count ?? - 1;
}
/**
* Create new download link from $url.
*
* @param string $url Download URL.
*
* @return string
*/
public static function create( string $url ): string {
global $wpdb;
$download_link = self::get_link( $url );
if ( $download_link ) {
return $download_link;
}
$download_link = self::$link_base . wp_hash( $url );
$data = [
'url' => $url,
'download_link' => $download_link,
];
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
$result = $wpdb->insert( self::$table, $data );
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery
return $result ? $download_link : '';
}
/**
* Rewrite download link by its url.
* Increase download count.
*/
public function rewrite_download_link(): void {
$uri = '';
if ( isset( $_SERVER['REQUEST_URI'] ) ) {
$uri = filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ), FILTER_SANITIZE_STRING );
}
$path = wp_parse_url( $uri, PHP_URL_PATH );
if ( 0 === strpos( trailingslashit( $path ), self::$link_base ) ) {
$download_link = untrailingslashit( $path );
$url = self::get_url( $download_link );
if ( $url ) {
$this->increment_count( $url );
self::download_file( $url );
}
}
}
/**
* Download file.
*
* @param string $url file url.
*/
private static function download_file( string $url ): void {
if ( ! $url ) {
return;
}
$file_path = realpath( untrailingslashit( ABSPATH ) . wp_make_link_relative( $url ) );
if ( ! $file_path ) {
return;
}
$file_name = rawurlencode( pathinfo( $file_path, PATHINFO_FILENAME ) );
$file_extension = rawurlencode( pathinfo( $file_path, PATHINFO_EXTENSION ) );
$file_size = filesize( $file_path );
$known_content_types = [
'html' => 'text/html',
'htm' => 'text/html',
'txt' => 'text/plain',
'jpg' => 'image/jpg',
'jpeg' => 'image/jpg',
'png' => 'image/png',
'gif' => 'image/gif',
'tiff' => 'image/tiff',
'pdf' => 'application/pdf',
'doc' => 'application/msword',
'docx' => 'application/msword',
'xls' => 'application/vnd.ms-excel',
'xlsx' => 'application/vnd.ms-excel',
'ppt' => 'application/vnd.ms-powerpoint',
'pptx' => 'application/vnd.ms-powerpoint',
'php' => 'text/plain',
'exe' => 'application/octet-stream',
'zip' => 'application/zip',
];
$content_type = 'application/force-download';
if ( array_key_exists( $file_extension, $known_content_types ) ) {
$content_type = $known_content_types[ $file_extension ];
}
header( 'Expires: 0' );
header( 'Cache-Control: no-cache, no-store, must-revalidate' );
header( 'Cache-Control: pre-check=0, post-check=0, max-age=0', false );
header( 'Pragma: no-cache' );
header( "Content-type: {$content_type}" );
header( "Content-Disposition:attachment; filename={$file_name}.{$file_extension}" );
header( 'Content-Transfer-Encoding: binary' );
header( "Content-Length: {$file_size}" );
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_readfile
readfile( $file_path );
exit();
}
/**
* Increment count.
*
* @param string $url Download URL.
*/
private function increment_count( string $url ): void {
global $wpdb;
if ( ! apply_filters( 'downloader_increment_count', true, $url ) ) {
return;
}
$count = self::get_count( $url );
$data = [ 'count' => ++ $count ];
$where = [ 'url' => $url ];
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
$wpdb->update( self::$table, $data, $where );
// phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment