Merging PHP download script into `functions.php`

I run a large mixtape download site on WordPress. I’m currently developing a new theme and I’d like to include as much site functionality into the theme’s functions.php.

For serving the downloads, I have several PHP scripts. A download is called by a visitor by clicking on URL (example): http://www.tjoonz.com/house.php?id=1234&file=post-slug.
The script sanitizes the parameters and serves the MP3 files from a private server directory. My main concern is that these scripts contain the database connection details, which is something I would much rather use $wpdb for.

Read More

Current scripts (for reference)

My apologies if this is a bit excessive, but it may come in handy later. You can probably skip this bit for now if you’re just reading my question.

PHP

<?php // Tjoonz.com download script v1.2

// test arguments
if(!isset($_REQUEST['id']) || empty($_REQUEST['id']) || !isset($_REQUEST['file']) || empty($_REQUEST['file'])) 
{
    // invalid argument(s), abort script
    header("HTTP/1.0 400 Bad Request");
    die();
}

// set variables
$current_time = time();
$user_ip = $_SERVER['REMOTE_ADDR'];
$post_id = $_GET['id'];
$file_requested = $_GET['file'];
$file_name = strip_tags($file_requested);
$file_path = '/home/tjoonz/audio/house/';
$locked = false;

// test if cookie 'downloadsAllowed' is set
// this prevents hotlinking on other domains for users who haven't visited Tjoonz.com yet
if ($_COOKIE["downloadsAllowed"] == "tj00nz")
{   
    $file_full = $file_path.$file_name;
    if (file_exists($file_full)) // test if requested mixtape exists
    {
        // establish connection to database for Play Counter test
        $user="#######";
        $password="#######";
        $database="#######";
        $host="#######";
        mysql_connect($host,$user,$password);
        mysql_select_db($database) or die(mysql_error());

        // set timerange to 2 hours ago (right now minus 7200 seconds)
        $timerange = $current_time - 7200;
        // get all records from 'playcount_lock' table
        $locks = mysql_query("SELECT * FROM playcount_lock WHERE request_time > $timerange") or die(mysql_error()); 

        // test if current IP address has already accessed this mixtape in the last 2 hours
        while ($lock = mysql_fetch_object($locks))
        {
            // set variables with data from record
            $lock_ip = $lock->ip_address;
            $lock_id = $lock->post_id;
            $lock_time = $lock->request_time;

            // compare record data with current user data
            if($lock_ip == $user_ip && $lock_id == $post_id && ($lock_time + 7200) >= $current_time)
            {
                // match found, break out of while loop and continue with script
                $locked = true;
                break;
            }
        }

        if (!$locked) // if playcounter is not locked, update the database
        { 
            // add a lock entry for the next two hours
            mysql_query("INSERT INTO `playcount_lock` (`request_time`, `post_id`, `ip_address`) VALUES ('$current_time', '$post_id', '$user_ip')") or die(mysql_error());

            // update the play counter for this mixtape
            $post_meta = mysql_query("SELECT meta_value FROM `wp_postmeta` WHERE `meta_key` = '_played' AND `post_id` = ".$post_id);
            $count = @mysql_result($post_meta, 0);

            if($count == FALSE)
            {
                // if no plays are present, insert the metadata into table
                mysql_query("INSERT INTO wp_postmeta (post_id, meta_key, meta_value) VALUES (".$post_id.",'_played',1)");
            }
            else
            {
                // otherwise increase the existing number of plays by 1
                $newCount = mysql_result($post_meta, 0) + 1;
                mysql_query("UPDATE `wp_postmeta` SET meta_value = ".$newCount." WHERE `meta_key` = '_played' AND `post_id` = ".$post_id);
            }
        }

        // we're done with the database, kill the connection
        mysql_close();

        // finally, send the file
        header("X-SENDFILE: ".$file_full);
        header("Content-Type: audio/mpeg");
        header("Content-Disposition: attachment; filename=".basename($file_full));
        header("Content-Length: ".filesize($file_full));
        header("Pragma: public");
        header("Expires: 0");
        header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
        header("Cache-Control: public");
        header("Content-Description: File Transfer");
        header("Content-Transfer-Encoding: binary");
    }
    else // couldn't find that mixtape
    {
        header('HTTP/1.0 404 Not Found');
    }

}
else // cookie 'downloadsAllowed' not set, redirecting user to mixtape page
{
    $file_redirection = substr($file_name,0, -4);
    header("Location: http://www.tjoonz.com/house/" . $file_redirection . "/");
}

?>

JS

// when playnow is clicked, add to myPlaylist and immediately start playing the mixtape
$("body").on("click", ".jplayer-playnow", function (e) {
    e.preventDefault();

    // add to Play Queue and play it immediately
    $.jPlayer.timeFormat.showHour = true;
    $.jPlayer.timeFormat.sepHour = ":";
    $.jPlayer.timeFormat.sepMin = ":";
    $.jPlayer.timeFormat.sepSec = "";
    myPlaylist.add({
        title: $(".single-title-mixtape").text(),
        artist: $(".single-title-artist").text(),
        genre: $(".jplayer-playnow span").attr("class"),
        mp3: $(this).attr("href"),                       // <--- This is where my problem comes up for my new theme, keep reading
        poster: $(".wp-post-image").attr("src")
    }, true); // true value here makes the newly added track play immediately
});

Intentions

I’m using the current script twofold: When a user clicks the PLAY button, jQuery prevents the link from being navigated to, and takes the URL to instruct a music player to get the file instead. When a user clicks the DOWNLOADS button, the browser just follows through and a direct download is initiated. Both buttons use the same scripts and the same URLs.

In my new script I want to combine house.php, electro.php, dubstep.php, drum-and-bass.php and techno.php with a single script that take a genre parameter. Additionally, depending on how things go from here, I’ll add a parameter to specify the action (download or play).

I intend to completely embed the script inside functions.php. On a mixtape page, two links would be:

<!--Play-->
<a href="?id=<?php echo $post->ID; ?>&file=<?php tjnz_slug(); ?>&genre=<?php echo $category[0]->category_nicename; ?>&action=play">Play</a>
<!--Download-->
<a href="?id=<?php echo $post->ID; ?>&file=<?php tjnz_slug(); ?>&genre=<?php echo $category[0]->category_nicename; ?>&action=download">Download</a>

The problem

In my old script, I simply caught play links (by class name on the anchor) and prevented the default browser action, then do my things. I was able to do this because I could point to a ‘real’ file (e.g. house.php).

If I were to stick the code inside functions.php, how can I instruct my music player where the audio file is located? The audio file doesn’t necessarily have to be an MP3 file, as is evident by my current code: I instruct jPlayer that the audio file is located at house.php for example.

How do I tell jPlayer that the file is now coming from the mysterious functions.php? Do I link directly to theme dir/functions.php? Doesn’t sound like a good idea, but that’s just my gut feeling.

Like I said before, the main reason I want the script to be inside functions.php is so that I can tap into the power of $wpdb. If a separate PHP file is still the way to go, I’d still accept that answer (provided you’ve backed up that theory).

Related posts

Leave a Reply

1 comment

  1. You won’t be calling stuff directly from the functions.php file by URL reference.

    The functions.php is loaded in with the WordPress theme and it makes functions available to you from within the theme template files.

    Most of your code can be ported directly into the functions.php but you will have to wrap some sort of interface around it which can be called within a page template.

    If I were doing this I would likely put some PHP code in the index.php of the theme, right a the top to have a look at what’s in the $_REQUEST object, like you do at the start of your PHP code.

    Then, rather than execute the PHP you already have, you would call functions that are within your functions.php file that deal with the actual DB connections and serving the file.

    As you mentioned, you’ll have to use the $wpdb global variable to read the tables and the good news is that because you’re working within the WordPress session, you don’t have to authenticate with the DB so no UN/PW info.

    Your JS will be best served in a separate file within your theme and loaded into WordPress properly by using wp_enqueue_script.