4
\$\begingroup\$

I'm a PHP novice and so looking for some advice on a PHP function I have created to use within a WordPress installation.

As you can see from the code below, it runs when one of the admin's press 'Publish' on a pending post.

It takes a Zip file that has been uploaded by a user via Gravity Forms, then unzips ONLY .mp3 extensions. Re-zips and moves all the files to a new folder in our Amazon S3 directory.

The code is pieced together from my limited knowledge and some help along the way with questions on here.

So, here's what I ended up with:

add_action('pending_to_publish', 'unzip_to_s3');  
function unzip_to_s3() { 
    global $post;
    global $wpdb;

    // Only run function if post is portfolio post type
    if ('portfolio' == $post->post_type) {

        // Set temp path
        $temp_path = '../wp-content/uploads/gravity_forms/1-9e5dc27086c8b2fd2e48678e1f54f98c/2013/02/tmp/';

        // Get filename from Zip file
        $file = get_post_meta($post->ID, 'file_url', true);
        $zip_file = basename($file);

        // Create full Zip file path
        $zip_file_path = $temp_path.$zip_file;

        // Generate unique name for temp sub_folder for unzipped files
        $temp_unzip_folder = uniqid('temp_TMS_', true);

        // Create full temp sub_folder path
        $temp_unzip_path = $temp_path.$temp_unzip_folder;

        // Make the new temp sub_folder for unzipped files
        if (!mkdir($temp_unzip_path, 0755, true)) {
            die('Error: Could not create path: '.$temp_unzip_path);
        }

        // Unzip files to temp unzip folder, ignoring anything that is not a .mp3 extension
        $zip = new ZipArchive();
        $filename = $zip_file_path;

        if ($zip->open($filename)!==TRUE) {
            exit("cannot open <$filename>\n");
        }

        for ($i=0; $i<$zip->numFiles;$i++) {
            $info = $zip->statIndex($i);
            $file = pathinfo($info['name']);
            if(strtolower($file['extension']) == "mp3") {
                file_put_contents($temp_unzip_path.'/'.basename($info['name']), $zip->getFromIndex($i));
            } else {
                $zip->deleteIndex($i);
            }
        }
        $zip->close();

        // Re-zip the unzipped mp3's and store new zip file in temp folder created earlier
        $temp_unzip_path = $temp_unzip_path.'/';
        $zip = new ZipArchive();
        $dirArray = array();
        $new_zip_file = $temp_unzip_path.$zip_file;

        $new = $zip->open($new_zip_file, ZIPARCHIVE::CREATE);
        if ($new === true) {
            $handle = opendir($temp_unzip_path);
            while (false !== ($entry = readdir($handle))) {
            if(!in_array($entry,array('.','..')))
            {
                $dirArray[] = $entry;
                $zip->addFile($temp_unzip_path.$entry,$entry);
            }
        }
        closedir($handle);
        } else {
            echo 'Failed to create Zip';
        }

        $zip->close();

        // Set Media bucket dir
        $bucket_path = '../wp-content/uploads/gravity_forms/1-9e5dc27086c8b2fd2e48678e1f54f98c/2013/02/mixtape2/';

        // Generate unique name for sub_bucket
        $sub_bucket = uniqid('TMS_', true);

        // Create full sub_bucket path
        $sub_bucket_path = $bucket_path.$sub_bucket;

        // Make the new sub_bucket
        if (!mkdir($sub_bucket_path, 0755, true)) {
            die('Error: Could not create path: '.$sub_bucket_path);
        }

        // Move mp3's to new sub_bucket 
        // Get array of all source files
        $files = scandir($temp_unzip_path);
        // Identify directories
        $source = $temp_unzip_path;
        $destination = $sub_bucket_path.'/';
        // Cycle through all source files
        foreach ($files as $file) {
            if (in_array($file, array(".",".."))) continue;
            // if move files is successful delete the original temp folder
            if (rename($source.$file, $destination.$file)) {
                rmdir($temp_unzip_path);
            }
        }

        // Delete original Zip file
        unlink($temp_path.$zip_file);

        // Update Custom field for new Zip file location
        update_post_meta($post->ID, 'file_url', 'http://themixtapesite.com/wp-content/uploads/gravity_forms/1-9e5dc27086c8b2fd2e48678e1f54f98c/2013/02/mixtape2/'.$sub_bucket.'/'.$zip_file);

    }
}

Whilst this function does work, we're dealing with large files and so it does take a while to process.

What is happening is when the admin presses publish it triggers this function but the page just sits there until it's finished this function and then will continue. This function can take up to around 5 minutes to run.

I'm looking to optimise this function (in terms of code) but also see if there's a way I can run this in the background so that the admin can carry on with other things and not have to sit there waiting around.

\$\endgroup\$
7
  • 1
    \$\begingroup\$ If you're going to delete files from the original zip file that are not mp3s, why bother to create a new one? You already have the one you want after the first loop. \$\endgroup\$
    – user23437
    Commented Mar 22, 2013 at 14:14
  • \$\begingroup\$ My logic behind that was to protect against zipbombs. If the unzip function is only looking at file extensions then it wouldn't see folder structures etc. so, i unzip all the mp3's. then rezip them knowing i have a clean zip file with no additional folder structures etc. \$\endgroup\$ Commented Mar 22, 2013 at 14:55
  • \$\begingroup\$ I don't really see any optimizations but I can point out a problem with your first loop.. Whenever you delete anything inside a loop, you need to rewind your loop by 1 so that you don't skip over the next in line. \$\endgroup\$
    – user23437
    Commented Mar 22, 2013 at 15:20
  • \$\begingroup\$ @Sosukodo Forgive me, but i'm not sure how to do that? My knowledge of loops is very limited unfortunately.. do you have a url to somewhere i can read up on what you've mentioned please? \$\endgroup\$ Commented Mar 22, 2013 at 15:32
  • \$\begingroup\$ Just echo the file name in that loop on a test zip to see what happens after deleteIndex is called.. deleteIndex may just use unset so you may not have anything to worry about. \$\endgroup\$
    – user23437
    Commented Mar 22, 2013 at 16:26

1 Answer 1

1
\$\begingroup\$

I'm not going to touch on how to improve the speed of the zip operation because it's always going to be a bottleneck no matter how much optimization you do. Instead you should focus on how to improve the experience for the end user so that they don't have to wait for the operation to complete. There are a couple of ways to do this with PHP like pcntl_fork, Server-Sent Events, or simply disconnecting the script from the user.

Forking is a bit overkill. Server-sent events would be neat to keep the client informed of progress, but it will only do that if your site is designed as a single page app (or the user doesn't leave the page). I think disconnecting the user is your best bet as it's the easiest one to accomplish. All you need to do is buffer your output to the user and set a header that tells the client the size of the content you're sending. Then tell PHP to keep running after client disconnect and you're all done.

Check out this stackoverflow answer by povilasp for a great simple example.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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