1

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.

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).

3
  • 1
    Maybe you should consider moving all this to a plugin and let functions.php deal with exclusively theme related issues. This Q&A may help to clarify things: Where to put my code: plugin or functions.php?
    – brasofilo
    Mar 21, 2013 at 13:23
  • It seems that, by that article's logic, this code would definitely belong inside a plugin. I personally avoid plugins so I'm not really proficient in that area. If I were to stick the code into a private plugin, how would I call its functions (as proposed by @user28206)? Is there an easy way to 'pluginify' my code? Mar 21, 2013 at 13:43
  • 1
    Well, the plugin being active, you can call its functions from the theme. The matter is not converting functions.php into a big mess and being able to swap themes and not lose this functionality. You can surely organize functions.php by including external files. Anyway, it's up to you follow one path or another after reading the shortcomings in that Q&A and linked/related ones. plugin-development isn't complex and is well documented here.
    – brasofilo
    Mar 21, 2013 at 13:51

1 Answer 1

2

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.

2
  • Okay, this is indeed a nice solution. I could then direct to http://www.tjoonz.com?<params> and the index PHP code would take the parameters and call the functions that are defined in functions.php. The pieces are falling into place now. I will try this approach out this weekend. In the mean time, have a nice +1 good sir! Mar 21, 2013 at 13:03
  • Extra question: is it possible to do a POST with AJAX to access functions.php directly, and receive the audio file as response? I am already using AJAX to update my database using functions that are defined in functions.php, using add_action('wp_ajax_tjnz-vote','tjnz_vote');. I'm not too familiar with AJAX yet, so I'm wondering if it could handle header responses from functions.php, and serve the audio file in, for example, $.ajax().success(function($audiostream) {...}); Mar 21, 2013 at 13:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.